# -*- coding: utf-8 -*-
"""舞台型ショートの部品（2026-11-06「研究のスキル一覧」で決めた見た目を、2本目から共通にした）。
モニターの中（900×600）は面で組む: 左上に小さな見出し、その下に言いたいことを1文、図は1つ。
オレンジは「いま話している1つ」だけ。細い枠線・記号のアイコンは使わない。
"""
import json, os
from kabe_kit import shake_js, counter_js

ORANGE, FACE_C, FACE_2, DIM, INK = "#E2683A", "#1B2433", "#2C3647", "#8A93A3", "#F5F3EE"
HERE = os.path.dirname(os.path.abspath(__file__))
STAGE_DIR = os.path.join(HERE, "stage")

CSS = f"""<style>
.win {{ position:absolute; inset:0; opacity:0; }}
.kick {{ position:absolute; left:48px; top:40px; font-size:26px; font-weight:800; color:{DIM}; letter-spacing:.04em; }}
.kick b {{ display:inline-block; width:14px; height:14px; background:{ORANGE}; border-radius:3px; margin-right:12px; vertical-align:2px; }}
.msg {{ position:absolute; left:48px; right:48px; top:96px; font-size:58px; font-weight:900; line-height:1.22; color:{INK}; }}
.card {{ position:absolute; border-radius:12px; background:{FACE_C}; color:{INK}; font-weight:900; display:flex; flex-direction:column; justify-content:center; padding:0 26px; }}
.card small {{ font-size:22px; color:{DIM}; font-weight:800; margin-bottom:4px; }}
.card.or {{ background:{ORANGE}; color:#fff; }} .card.or small {{ color:#ffe1d4; }}
.line {{ position:absolute; height:16px; border-radius:8px; background:{FACE_2}; }}
.hero {{ position:absolute; font-weight:900; color:{ORANGE}; line-height:.9; letter-spacing:-.03em; }}
.hero span {{ font-size:.32em; color:{INK}; letter-spacing:0; margin-left:8px; }}
.arrowR {{ position:absolute; width:0; height:0; border-top:16px solid transparent; border-bottom:16px solid transparent; border-left:22px solid {FACE_2}; }}
.chk {{ position:absolute; left:48px; right:48px; height:76px; border-radius:10px; background:{FACE_C}; display:flex; align-items:center; gap:22px; padding:0 22px; font-size:36px; font-weight:800; color:{INK}; opacity:0; }}
.chk i {{ font-style:normal; width:48px; height:48px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:30px; font-weight:900; background:#3A4558; color:{INK}; }}
.chk.ng i {{ background:{ORANGE}; }} .chk.ng {{ color:{ORANGE}; }}
.bubble {{ position:absolute; left:48px; padding:16px 26px; border-radius:18px 18px 18px 4px; background:{FACE_C}; font-size:32px; font-weight:800; color:{INK}; opacity:0; }}
.strike {{ position:absolute; height:10px; border-radius:5px; background:{ORANGE}; transform-origin:left; transform:scaleX(0); }}
.stamp {{ position:absolute; background:{ORANGE}; color:#fff; font-weight:900; padding:6px 28px; border-radius:10px; opacity:0; white-space:nowrap; }}
.qm {{ position:absolute; left:0; right:0; top:60px; text-align:center; font-size:400px; font-weight:900; line-height:1; color:{ORANGE}; }}
@keyframes show {{ to {{ opacity:1; }} }}
@keyframes hide {{ to {{ opacity:0; }} }}
@keyframes up {{ from {{ opacity:0; transform:translateY(24px); }} to {{ opacity:1; transform:none; }} }}
@keyframes inR {{ from {{ opacity:0; transform:translateX(40px); }} to {{ opacity:1; transform:none; }} }}
@keyframes on {{ to {{ background:{ORANGE}; color:#fff; }} }}
@keyframes dim {{ to {{ opacity:.22; }} }}
@keyframes blurOut {{ to {{ opacity:0; filter:blur(14px); transform:translateY(-30px); }} }}
@keyframes strike {{ to {{ transform:scaleX(1); }} }}
@keyframes slam {{ 0% {{ opacity:0; transform:rotate(-6deg) scale(2.2); }} 80% {{ opacity:1; transform:rotate(-6deg) scale(.96); }} 100% {{ opacity:1; transform:rotate(-6deg) scale(1); }} }}
@keyframes pop {{ 0% {{ opacity:0; transform:scale(.6); }} 70% {{ opacity:1; transform:scale(1.04); }} 100% {{ opacity:1; transform:none; }} }}
@keyframes breathe {{ 0%,100% {{ transform:scale(1); }} 50% {{ transform:scale(1.05); }} }}
@keyframes grow {{ from {{ transform:scaleX(0); }} to {{ transform:scaleX(1); }} }}
</style>"""


def win(t0, t1, inner):
    an = f"show .15s linear {t0:.3f}s forwards" + (f", hide .15s linear {t1:.3f}s forwards" if t1 else "")
    return f'<div class="win" style="animation:{an}">{inner}</div>'


def kick(name):
    return f'<div class="kick"><b></b>{name}</div>'


def a(name, t, d=.3, ease="ease-out"):
    """animation の1つ分"""
    return f"{name} {d}s {ease} {t:.3f}s forwards"


def stage_fx(T, bursts=(), shakes=(), ring_sec=48.0, fx=()):
    """床の円を回す（stage_bg_noring.png の上にカメラの値で描き直す）＋紙吹雪（bursts: [(秒, 片の数)]）＋画面の揺れ
    fx: 紙吹雪のほかの演出 [dict(kind=, t=秒, d=長さ, ...)]（毎回紙吹雪にしない・2026-11-06 エイイチの指示）
      rays  … 床の円の真ん中から光の筋が回る（お祝い・決まった所）
      speed … マンガの集中線がモニターに集まる（叩きつけ・ツッコミ）
      shock … 床の円から波紋が広がる（知らせ・音・決定）
      spot  … まわりを暗くして、x の位置にスポットライト（決めるのは○○）
      stars … 上の方に星がまたたき、少し暗くなる（夜・ひらめき）
      shine … モニターの上を光の帯が斜めに走る（作り直した・きれいになった）
      rise  … 床から小さな四角が立ちのぼる（データ・記録・読まれる）"""
    R = json.load(open(os.path.join(STAGE_DIR, "ring_lut.json")))
    bs = [dict(t=t, n=n) for t, n in bursts]
    js = f"""<canvas id="fx" width="1080" height="1920" style="position:absolute;left:0;top:0;z-index:3;pointer-events:none"></canvas>
<script>
(function(){{
const LUT={json.dumps(R["lut"])}, FY={R["fy"]:.4f}, C={json.dumps(R["C"])}, PIT={R["pitch"]}*Math.PI/180, SEC={ring_sec};
const f1=-Math.sin(PIT), f2=Math.cos(PIT), u1=Math.cos(PIT), u2=Math.sin(PIT);
function P(x,z){{ const dx=x-C[0], dy=-C[1], dz=z-C[2]; const zc=dy*f1+dz*f2, yc=dy*u1+dz*u2; return [540+FY*dx/zc, 960-FY*yc/zc]; }}
function rnd(seed){{ return function(){{ seed|=0; seed=seed+0x6D2B79F5|0; let t=Math.imul(seed^seed>>>15,1|seed); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)/4294967296; }}; }}
const COLS=["#E2683A","#F2C14E","#F5F3EE","#57B5F8","#FFB38A","#F2C14E","#E2683A"];
const BURSTS={json.dumps(bs)};
const FX={json.dumps(list(fx))};
const parts=[];
BURSTS.forEach((b,bi)=>{{ const r=rnd(7+bi*101);
  for(let i=0;i<b.n;i++){{ const side=i%2?1:-1; const x0=540+side*400, y0=1470;
    const ang=(-90+side*-28+(r()-.5)*44)*Math.PI/180, sp=1300+r()*1300;
    parts.push({{t0:b.t+r()*0.18, x0, y0, vx:Math.cos(ang)*sp, vy:Math.sin(ang)*sp, k:2.6+r()*1.4, g:900+r()*300,
      sh:(r()<.12?3:(r()<.25?1:(r()<.45?2:0))), w:5+r()*6, c:COLS[Math.floor(r()*COLS.length)], rs:(r()-.5)*14, r0:r()*6.28,
      fl:6+r()*10, ph:r()*6.28, sw:16+r()*26, life:2.6+r()*1.4}}); }} }});
const cv=document.getElementById('fx'), g=cv.getContext('2d');
function ring(t){{ const N=36, rot=t*2*Math.PI/SEC;
  for(let k=0;k<N;k++){{ const a0=rot+k*2*Math.PI/N, a1=a0+Math.PI/N, am=(a0+a1)/2;
    let idx=Math.floor((((am%(2*Math.PI))+2*Math.PI)%(2*Math.PI))/(2*Math.PI)*36)%36; const c=LUT[idx];
    const pts=[]; for(let j=0;j<=6;j++){{ const a=a0+(a1-a0)*j/6; pts.push(P(1.55*Math.cos(a),1.55*Math.sin(a))); }}
    for(let j=6;j>=0;j--){{ const a=a0+(a1-a0)*j/6; pts.push(P(1.45*Math.cos(a),1.45*Math.sin(a))); }}
    g.beginPath(); g.moveTo(pts[0][0],pts[0][1]); pts.slice(1).forEach(q=>g.lineTo(q[0],q[1])); g.closePath();
    g.fillStyle=`rgb(${{Math.min(255,c[0]*1.15)}},${{Math.min(255,c[1]*1.15)}},${{Math.min(255,c[2]*1.15)}})`; g.fill(); }} }}
function confetti(t){{ for(const p of parts){{ const u=t-p.t0; if(u<0||u>p.life) continue;
    const e=(1-Math.exp(-p.k*u))/p.k; let x=p.x0+p.vx*e, y=p.y0+(p.vy+p.g/p.k)*e-p.g*u/p.k;
    x+=p.sw*Math.sin(p.fl*0.5*u+p.ph)*Math.min(1,u/0.6);
    const al=u>p.life-0.6?(p.life-u)/0.6:1; g.save(); g.globalAlpha=al; g.translate(x,y); g.rotate(p.r0+p.rs*u);
    const fs=Math.max(.15,Math.abs(Math.cos(p.fl*u+p.ph)));
    if(p.sh===3){{ const tw=0.5+0.5*Math.sin(u*18+p.ph); g.fillStyle="#FFF7E0"; g.globalAlpha=al*tw; const s=p.w*1.3;
      g.beginPath(); g.moveTo(0,-s); g.lineTo(s*.25,-s*.25); g.lineTo(s,0); g.lineTo(s*.25,s*.25); g.lineTo(0,s); g.lineTo(-s*.25,s*.25); g.lineTo(-s,0); g.lineTo(-s*.25,-s*.25); g.closePath(); g.fill(); }}
    else {{ g.scale(1,fs); g.fillStyle=p.c;
      if(p.sh===1){{ g.beginPath(); g.arc(0,0,p.w*.55,0,6.283); g.fill(); }}
      else if(p.sh===2){{ g.fillRect(-p.w*.22,-p.w*1.6,p.w*.44,p.w*3.2); }}
      else {{ g.fillRect(-p.w/2,-p.w*.8,p.w,p.w*1.6); }} }}
    g.restore(); }} }}

const SCR=[90,470,990,1070];
function outClip(){{ g.beginPath(); g.rect(0,0,1080,1920); g.rect(SCR[0],SCR[1],SCR[2]-SCR[0],SCR[3]-SCR[1]); g.clip('evenodd'); }}
function env(u,d){{ if(u<0||u>d) return 0; return Math.min(1,u/0.25)*Math.min(1,(d-u)/0.5); }}
function fxRays(u,e){{ const al=env(u,e.d)*0.35, cx=540, cy=1500, n=14, rot=u*0.6;
  g.save(); outClip(); g.globalAlpha=al; for(let k=0;k<n;k++){{ const a0=rot+k*2*Math.PI/n, a1=a0+Math.PI/n*0.55;
    const gr=g.createRadialGradient(cx,cy,0,cx,cy,1500); gr.addColorStop(0,'rgba(255,214,140,1)'); gr.addColorStop(1,'rgba(255,214,140,0)');
    g.fillStyle=gr; g.beginPath(); g.moveTo(cx,cy); g.arc(cx,cy,1500,a0,a1); g.closePath(); g.fill(); }} g.restore(); }}
function fxSpeed(u,e){{ if(u<0||u>e.d) return; const r=rnd(Math.floor(u*20)+11), cx=540, cy=770; g.save(); g.globalAlpha=Math.min(1,(e.d-u)/0.3)*0.85; g.fillStyle='#fff';
  for(let k=0;k<70;k++){{ const a=r()*6.283, w=0.004+r()*0.01, r0=420+r()*160, r1=1300;
    g.beginPath(); g.moveTo(cx+Math.cos(a-w)*r1, cy+Math.sin(a-w)*r1); g.lineTo(cx+Math.cos(a)*r0, cy+Math.sin(a)*r0); g.lineTo(cx+Math.cos(a+w)*r1, cy+Math.sin(a+w)*r1); g.closePath(); g.fill(); }}
  g.restore(); }}
function fxShock(u,e){{ for(let w=0;w<3;w++){{ const v=u-w*0.35; if(v<0||v>1.4) continue; const rr=0.3+v*2.4, al=(1-v/1.4)*0.9;
    g.save(); g.globalAlpha=al; g.strokeStyle=e.c||'#E2683A'; g.lineWidth=10*(1-v/1.4)+2; g.beginPath();
    for(let j=0;j<=72;j++){{ const a=j/72*6.283, q=P(rr*Math.cos(a), rr*Math.sin(a)); j?g.lineTo(q[0],q[1]):g.moveTo(q[0],q[1]); }} g.stroke(); g.restore(); }} }}
function fxSpot(u,e){{ const al=env(u,e.d); if(!al) return; g.save(); outClip(); g.globalAlpha=al*0.78; g.fillStyle='#000';
  g.beginPath(); g.rect(0,0,1080,1920); g.ellipse(e.x, 1500, 230, 70, 0, 0, 6.283); g.fill('evenodd');
  g.globalAlpha=al*0.22; const gr=g.createLinearGradient(0,600,0,1520); gr.addColorStop(0,'rgba(255,240,210,0)'); gr.addColorStop(1,'rgba(255,240,210,1)');
  g.fillStyle=gr; g.beginPath(); g.moveTo(e.x-60,560); g.lineTo(e.x+60,560); g.lineTo(e.x+230,1500); g.lineTo(e.x-230,1500); g.closePath(); g.fill(); g.restore(); }}
function star(x,y,s){{ g.beginPath(); g.moveTo(x,y-s); g.lineTo(x+s*.22,y-s*.22); g.lineTo(x+s,y); g.lineTo(x+s*.22,y+s*.22); g.lineTo(x,y+s); g.lineTo(x-s*.22,y+s*.22); g.lineTo(x-s,y); g.lineTo(x-s*.22,y-s*.22); g.closePath(); g.fill(); }}
function fxStars(u,e){{ const al=env(u,e.d); if(!al) return; g.save(); outClip(); g.globalAlpha=al*0.45; g.fillStyle='#05070d'; g.fillRect(0,0,1080,1920);
  const r=rnd(5); g.fillStyle='#FFF4D6'; for(let k=0;k<46;k++){{ let x=r()*1080, y=r()*1900; if(x>70&&x<1010&&y>450&&y<1090){{ y=y<770?450-r()*300:1090+r()*120; }}
    const ph=r()*6.283, sp=2+r()*4, s=4+r()*10; g.globalAlpha=al*(0.35+0.65*Math.max(0,Math.sin(u*sp+ph))); star(x,y,s); }} g.restore(); }}
function fxShine(u,e){{ if(u<0||u>e.d) return; const v=u/e.d, x=SCR[0]-300+(SCR[2]-SCR[0]+600)*v; g.save(); g.beginPath(); g.rect(SCR[0],SCR[1],SCR[2]-SCR[0],SCR[3]-SCR[1]); g.clip();
  const gr=g.createLinearGradient(x-120,0,x+120,0); gr.addColorStop(0,'rgba(255,255,255,0)'); gr.addColorStop(.5,'rgba(255,255,255,.55)'); gr.addColorStop(1,'rgba(255,255,255,0)');
  g.fillStyle=gr; g.translate(x,770); g.rotate(0.35); g.fillRect(-120,-900,240,1800); g.restore(); }}
function fxRise(u,e){{ const r=rnd(9); const cols=['#E2683A','#57B5F8','#F5F3EE']; for(let k=0;k<70;k++){{ const t0=r()*e.d*0.7, life=1.6+r()*1.2, v=u-t0; if(v<0||v>life) continue;
    const x0=200+r()*680, y=1560-v*(380+r()*260), x=x0+Math.sin(v*3+k)*14, s=6+r()*8; g.save(); g.globalAlpha=Math.min(1,v/0.2)*Math.min(1,(life-v)/0.5)*0.9; g.fillStyle=cols[k%3]; g.fillRect(x,y,s,s); g.restore(); }} }}
const FXF={{rays:fxRays,speed:fxSpeed,shock:fxShock,spot:fxSpot,stars:fxStars,shine:fxShine,rise:fxRise}};
function effects(t,front){{ for(const e of FX){{ const back=(e.kind==='rays'||e.kind==='shock'||e.kind==='rise'); if(back===front) continue; FXF[e.kind](t-e.t,e); }} }}
const prev=window.hook;
window.hook=function(t){{ if(prev) prev(t); g.clearRect(0,0,1080,1920); effects(t,false); ring(t); effects(t,true); confetti(t); }};
window.hookBusy=function(t){{ return true; }};
window.hook(0);
}})();
</script>"""
    return js + (shake_js(list(shakes), amp=7) if shakes else "")


# 舞台型の共通の値（spec.py で from butai_parts import * して使う）
STAGE = STAGE_DIR
BG_NAME = "stage_bg_noring"
FOOT_L, FOOT_R, SIZE, SUB_MID = (390, 1525), (690, 1525), 0.88, 1590
SUB_ONE_LINE = True
LEAD = 0.8

OUTRO_TEXT = "研究の記録は、毎日サイトで公開中です！"


def outro(T, k):
    """締め（サイトへの入口）。モニターを pixelstomotors.com で覆う。文 k は narration.py の最後に足した OUTRO_TEXT の文
    （2026-11-07 エイイチ「うちのサイトへの動線部分がないけど？」。2本目からの8本で入れ忘れていた）"""
    t = T(k, 0)
    return (f'<div class="win" style="z-index:10;background:#0E1420;animation:show .2s linear {t:.3f}s forwards">'
            f'<div class="kick"><b></b>研究の記録</div>'
            f'<div style="position:absolute;left:0;right:0;top:210px;text-align:center;font-size:76px;font-weight:900;color:{INK};opacity:0;animation:pop .4s ease-out {t + 0.25:.3f}s forwards">pixelstomotors<span style="color:{ORANGE}">.com</span></div>'
            f'<div style="position:absolute;left:0;right:0;top:330px;text-align:center;font-size:38px;font-weight:800;color:{DIM};opacity:0;animation:up .4s ease-out {t + 0.9:.3f}s forwards">毎日、公開中</div></div>')
