/* =========================================================
   三藏閣 — 坐禪 · 명상
   ---------------------------------------------------------
   상태: 'setup' → 'running' → 'done'
   - 시간: 롤러 픽커(스크롤 스냅)로 프리셋(분) 선택
   - 음원(오행): 무음 / 불 / 물 / 바람 / 땅
   - 모든 음원은 Web Audio API 합성 (외부 파일 의존 X)
   ========================================================= */

(function () {
  /* ---------- 시간 프리셋 (분) — 롤러 픽커 데이터 (1 ~ 180) ---------- */
  const TIME_MIN = 1;
  const TIME_MAX = 180;
  const TIME_PRESETS = Array.from(
    { length: TIME_MAX - TIME_MIN + 1 },
    (_, i) => ({ m: i + TIME_MIN })
  );

  const fmtPreset = (m) => {
    if (m < 60) return { num: String(m), unit: '분' };
    if (m % 60 === 0) return { num: String(m / 60), unit: '시간' };
    return { num: `${Math.floor(m / 60)}·${m % 60}`, unit: '시간' };
  };

  /* ---------- 음원: 사대 + 무음 ---------- */
  const SOUND_OPTIONS = [
    { id: 'silence', kr: '무음', desc: '소리 없이' },
    { id: 'fire',    kr: '불',   desc: '장작이 타는 소리' },
    { id: 'water',   kr: '물',   desc: '흐르는 시냇물' },
    { id: 'wind',    kr: '바람', desc: '숲의 바람' },
    { id: 'earth',   kr: '땅',   desc: '낮게 울리는 대지' },
  ];

  /* ---------- 아이콘 (얇은 잉크선) ---------- */
  const ElemIcon = ({ kind, size = 26 }) => {
    const common = {
      width: size, height: size, viewBox: '0 0 24 24',
      fill: 'none', stroke: 'currentColor',
      strokeWidth: 1.3, strokeLinecap: 'round', strokeLinejoin: 'round',
      'aria-hidden': true,
    };
    switch (kind) {
      case 'silence':
        /* 가느다란 한 줄 — 침묵 */
        return (
          <svg {...common}>
            <line x1="6" y1="12" x2="18" y2="12"/>
          </svg>
        );
      case 'fire':
        /* 불꽃 한 송이 */
        return (
          <svg {...common}>
            <path d="M12 3.2c.6 2.6-1.4 3.7-2.6 5.6-1.3 2.1-1.4 4.6.4 6.2 1.9 1.8 5 1.6 6.5-.5 1.3-1.8.8-3.7-.4-5.3-.6 1.4-1.6 1.7-2.3 1.2 1.1-2 1-4.3-1.6-7.2z"/>
            <path d="M10.2 13.3c-.4 1.4.3 2.6 1.8 2.7 1.5.1 2.4-1 2.1-2.4-.5.7-1.2.9-1.8.6.4-.9.2-1.8-.4-2.6-.5.7-1.4 1.1-1.7 1.7z"/>
          </svg>
        );
      case 'water':
        /* 두 줄의 물결 */
        return (
          <svg {...common}>
            <path d="M3.5 9.5c1.6-1.6 3.2-1.6 4.8 0s3.2 1.6 4.8 0 3.2-1.6 4.8 0 1.6 1.6 2.6 1"/>
            <path d="M3.5 14.5c1.6-1.6 3.2-1.6 4.8 0s3.2 1.6 4.8 0 3.2-1.6 4.8 0 1.6 1.6 2.6 1"/>
          </svg>
        );
      case 'wind':
        /* 흐르는 바람 — 끝이 말리는 세 가닥 */
        return (
          <svg {...common}>
            <path d="M3.5 7.5h10.5a2.2 2.2 0 1 0-2.2-2.2"/>
            <path d="M3.5 12h13a2.5 2.5 0 1 1-2.5 2.5"/>
            <path d="M3.5 16.5h8"/>
          </svg>
        );
      case 'earth':
        /* 곤괘 ☷ — 음효 셋이 쌓인 모양 (땅) */
        return (
          <svg {...common} strokeWidth="1.6">
            <line x1="5"    y1="7"  x2="10.6" y2="7"/>
            <line x1="13.4" y1="7"  x2="19"   y2="7"/>
            <line x1="5"    y1="12" x2="10.6" y2="12"/>
            <line x1="13.4" y1="12" x2="19"   y2="12"/>
            <line x1="5"    y1="17" x2="10.6" y2="17"/>
            <line x1="13.4" y1="17" x2="19"   y2="17"/>
          </svg>
        );
      default:
        return null;
    }
  };

  /* =========================================================
     Web Audio 합성기
     - 종소리(singing bowl): 기본 + 배음 + 옅은 비브라토
     - 풍경(wind chime): 무작위 시간 간격으로 짧은 종
     - 물(stream): brown noise + low-pass filter modulation
     - 비(rain): pink noise + 약한 high-pass + 가끔 빗방울
     ========================================================= */
  let audioCtx = null;
  const ensureCtx = () => {
    if (!audioCtx) {
      const AC = window.AudioContext || window.webkitAudioContext;
      if (!AC) return null;
      audioCtx = new AC();
    }
    if (audioCtx.state === 'suspended') {
      try { audioCtx.resume(); } catch (_) {}
    }
    return audioCtx;
  };

  const ringBell = (when = 0, baseFreq = 196) => {
    const ctx = ensureCtx();
    if (!ctx) return;
    const t0 = ctx.currentTime + when;
    /* 배음 시리즈 — 신징보울 톤 */
    const partials = [
      { ratio: 1.0,  gain: 0.32, decay: 6.0 },
      { ratio: 2.01, gain: 0.18, decay: 4.5 },
      { ratio: 3.02, gain: 0.10, decay: 3.0 },
      { ratio: 5.10, gain: 0.05, decay: 2.0 },
    ];
    partials.forEach(p => {
      const osc = ctx.createOscillator();
      const g = ctx.createGain();
      osc.type = 'sine';
      osc.frequency.value = baseFreq * p.ratio;
      /* 옅은 비브라토 */
      const lfo = ctx.createOscillator();
      const lfoGain = ctx.createGain();
      lfo.frequency.value = 5.5;
      lfoGain.gain.value = baseFreq * p.ratio * 0.0025;
      lfo.connect(lfoGain).connect(osc.frequency);
      lfo.start(t0);
      lfo.stop(t0 + p.decay + 0.5);

      g.gain.setValueAtTime(0, t0);
      g.gain.linearRampToValueAtTime(p.gain, t0 + 0.008);
      g.gain.exponentialRampToValueAtTime(0.0001, t0 + p.decay);
      osc.connect(g).connect(ctx.destination);
      osc.start(t0);
      osc.stop(t0 + p.decay + 0.4);
    });
  };

  const makeNoiseBuffer = (ctx, seconds = 3, type = 'white') => {
    const len = ctx.sampleRate * seconds;
    const buf = ctx.createBuffer(1, len, ctx.sampleRate);
    const d = buf.getChannelData(0);
    if (type === 'brown') {
      let lastOut = 0;
      for (let i = 0; i < len; i++) {
        const wn = Math.random() * 2 - 1;
        lastOut = (lastOut + 0.02 * wn) / 1.02;
        d[i] = lastOut * 3.5;
      }
    } else if (type === 'pink') {
      let b0=0,b1=0,b2=0,b3=0,b4=0,b5=0,b6=0;
      for (let i = 0; i < len; i++) {
        const wn = Math.random() * 2 - 1;
        b0 = 0.99886 * b0 + wn * 0.0555179;
        b1 = 0.99332 * b1 + wn * 0.0750759;
        b2 = 0.96900 * b2 + wn * 0.1538520;
        b3 = 0.86650 * b3 + wn * 0.3104856;
        b4 = 0.55000 * b4 + wn * 0.5329522;
        b5 = -0.7616 * b5 - wn * 0.0168980;
        d[i] = (b0 + b1 + b2 + b3 + b4 + b5 + b6 + wn * 0.5362) * 0.11;
        b6 = wn * 0.115926;
      }
    } else {
      for (let i = 0; i < len; i++) d[i] = Math.random() * 2 - 1;
    }
    return buf;
  };

  /* 현재 재생 중인 ambient 핸들 */
  let current = null;
  const stopAll = () => {
    if (!current) return;
    try { current.stop(); } catch (_) {}
    current = null;
  };

  const startAmbient = (kind) => {
    const ctx = ensureCtx();
    if (!ctx) return;
    stopAll();

    if (kind === 'water') {
      /* 水 — 흐르는 시냇물: brown noise + lowpass, 천천한 LFO */
      const src = ctx.createBufferSource();
      src.buffer = makeNoiseBuffer(ctx, 4, 'brown');
      src.loop = true;
      const lp = ctx.createBiquadFilter();
      lp.type = 'lowpass';
      lp.frequency.value = 900;
      lp.Q.value = 0.8;
      const lfo = ctx.createOscillator();
      const lfoGain = ctx.createGain();
      lfo.frequency.value = 0.17;
      lfoGain.gain.value = 220;
      lfo.connect(lfoGain).connect(lp.frequency);
      const g = ctx.createGain();
      g.gain.value = 0.30;
      src.connect(lp).connect(g).connect(ctx.destination);
      src.start(); lfo.start();
      current = {
        stop: () => { try { src.stop(); } catch(_){} try { lfo.stop(); } catch(_){} },
        kind,
      };

    } else if (kind === 'fire') {
      /* 火 — 장작 불: 따뜻하게 필터된 분홍노이즈 + 무작위 크래클 */
      const src = ctx.createBufferSource();
      src.buffer = makeNoiseBuffer(ctx, 4, 'pink');
      src.loop = true;
      const lp = ctx.createBiquadFilter();
      lp.type = 'lowpass';
      lp.frequency.value = 650;
      lp.Q.value = 0.5;
      const hp = ctx.createBiquadFilter();
      hp.type = 'highpass';
      hp.frequency.value = 120;
      const g = ctx.createGain();
      g.gain.value = 0.28;
      /* 부드러운 호흡감 — 매우 느린 amp LFO */
      const ampLfo = ctx.createOscillator();
      const ampLfoG = ctx.createGain();
      ampLfo.frequency.value = 0.09;
      ampLfoG.gain.value = 0.06;
      ampLfo.connect(ampLfoG).connect(g.gain);
      src.connect(hp).connect(lp).connect(g).connect(ctx.destination);
      src.start(); ampLfo.start();

      let timer = null;
      const crackle = () => {
        const t = ctx.currentTime;
        const burst = 2 + Math.floor(Math.random() * 4);
        for (let i = 0; i < burst; i++) {
          const osc = ctx.createOscillator();
          const og = ctx.createGain();
          osc.type = 'square';
          osc.frequency.value = 900 + Math.random() * 2400;
          const startT = t + i * 0.012 + Math.random() * 0.04;
          og.gain.setValueAtTime(0, startT);
          og.gain.linearRampToValueAtTime(0.04 + Math.random() * 0.06, startT + 0.002);
          og.gain.exponentialRampToValueAtTime(0.0001, startT + 0.04 + Math.random() * 0.05);
          osc.connect(og).connect(ctx.destination);
          osc.start(startT);
          osc.stop(startT + 0.15);
        }
        timer = setTimeout(crackle, 180 + Math.random() * 1300);
      };
      timer = setTimeout(crackle, 400);
      current = {
        stop: () => {
          try { src.stop(); } catch(_){}
          try { ampLfo.stop(); } catch(_){}
          clearTimeout(timer);
        },
        kind,
      };

    } else if (kind === 'wind') {
      /* 風 — 숲의 바람: 분홍노이즈 + 천천히 움직이는 밴드패스 + 음량 스웰 */
      const src = ctx.createBufferSource();
      src.buffer = makeNoiseBuffer(ctx, 4, 'pink');
      src.loop = true;
      const bp = ctx.createBiquadFilter();
      bp.type = 'bandpass';
      bp.frequency.value = 700;
      bp.Q.value = 0.7;
      const lfo = ctx.createOscillator();
      const lfoG = ctx.createGain();
      lfo.frequency.value = 0.08;
      lfoG.gain.value = 450;
      lfo.connect(lfoG).connect(bp.frequency);
      const g = ctx.createGain();
      g.gain.value = 0.36;
      const ampLfo = ctx.createOscillator();
      const ampLfoG = ctx.createGain();
      ampLfo.frequency.value = 0.12;
      ampLfoG.gain.value = 0.18;
      ampLfo.connect(ampLfoG).connect(g.gain);
      src.connect(bp).connect(g).connect(ctx.destination);
      src.start(); lfo.start(); ampLfo.start();
      current = {
        stop: () => {
          try { src.stop(); } catch(_){}
          try { lfo.stop(); } catch(_){}
          try { ampLfo.stop(); } catch(_){}
        },
        kind,
      };

    } else if (kind === 'earth') {
      /* 土 — 낮게 울리는 대지: 저주파 brown noise + 55Hz 사인 드론 */
      const src = ctx.createBufferSource();
      src.buffer = makeNoiseBuffer(ctx, 4, 'brown');
      src.loop = true;
      const lp = ctx.createBiquadFilter();
      lp.type = 'lowpass';
      lp.frequency.value = 220;
      const g1 = ctx.createGain();
      g1.gain.value = 0.42;
      src.connect(lp).connect(g1).connect(ctx.destination);
      src.start();

      const osc = ctx.createOscillator();
      const g2 = ctx.createGain();
      osc.type = 'sine';
      osc.frequency.value = 55;
      g2.gain.value = 0.10;
      /* 미세하게 떨리는 펄스 */
      const ampLfo = ctx.createOscillator();
      const ampLfoG = ctx.createGain();
      ampLfo.frequency.value = 0.06;
      ampLfoG.gain.value = 0.05;
      ampLfo.connect(ampLfoG).connect(g2.gain);
      osc.connect(g2).connect(ctx.destination);
      osc.start(); ampLfo.start();

      /* 두 번째 옥타브로 깊이 더하기 */
      const osc2 = ctx.createOscillator();
      const g3 = ctx.createGain();
      osc2.type = 'sine';
      osc2.frequency.value = 82.5;
      g3.gain.value = 0.05;
      osc2.connect(g3).connect(ctx.destination);
      osc2.start();

      current = {
        stop: () => {
          try { src.stop(); } catch(_){}
          try { osc.stop(); } catch(_){}
          try { osc2.stop(); } catch(_){}
          try { ampLfo.stop(); } catch(_){}
        },
        kind,
      };

    } else {
      /* silence — 아무것도 재생하지 않음 */
      current = null;
    }
  };

  /* =========================================================
     시간 롤러 픽커 — 스크롤 스냅 기반
     ========================================================= */
  const ITEM_H = 44;
  const ITEM_H_DIAL = 56;
  const TimeRoller = ({ value, onChange, variant }) => {
    const isDial = variant === 'dial';
    const itemH = isDial ? ITEM_H_DIAL : ITEM_H;
    const vpRef = React.useRef(null);
    const targetIdxRef = React.useRef(
      Math.max(0, TIME_PRESETS.findIndex(p => p.m === value))
    );
    const [activeIdx, setActiveIdx] = React.useState(targetIdxRef.current);
    const programmaticRef = React.useRef(false);
    const scrollEndTimer = React.useRef(null);

    /* 마운트 시 현재 값 위치로 점프 — 스크롤 스냅이 안정될 때까지 여러 번 시도 */
    React.useEffect(() => {
      const idx = Math.max(0, TIME_PRESETS.findIndex(p => p.m === value));
      const apply = () => {
        const el = vpRef.current;
        if (!el) return;
        programmaticRef.current = true;
        /* scrollTo로 명시적 설정 */
        el.scrollTo({ top: idx * itemH, behavior: 'auto' });
        targetIdxRef.current = idx;
        setActiveIdx(idx);
        /* 다음 프레임에 해제 */
        requestAnimationFrame(() => {
          requestAnimationFrame(() => { programmaticRef.current = false; });
        });
      };
      /* 여러 시점에 시도해 스냅·레이아웃 타이밍을 회피 */
      const id1 = requestAnimationFrame(apply);
      const id2 = setTimeout(apply, 80);
      const id3 = setTimeout(apply, 240);
      return () => {
        cancelAnimationFrame(id1);
        clearTimeout(id2);
        clearTimeout(id3);
      };
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    const handleScroll = () => {
      const el = vpRef.current;
      if (!el) return;
      const idxFloat = el.scrollTop / itemH;
      const idx = Math.max(0, Math.min(TIME_PRESETS.length - 1, Math.round(idxFloat)));
      if (idx !== activeIdx) setActiveIdx(idx);
      if (!programmaticRef.current) {
        clearTimeout(scrollEndTimer.current);
        scrollEndTimer.current = setTimeout(() => {
          const m = TIME_PRESETS[idx].m;
          if (m !== value) onChange(m);
        }, 90);
      }
    };

    const goTo = (i) => {
      const el = vpRef.current;
      if (!el) return;
      el.scrollTo({ top: i * itemH, behavior: 'smooth' });
    };

    const onKey = (e) => {
      if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
        e.preventDefault();
        goTo(Math.min(TIME_PRESETS.length - 1, activeIdx + 1));
      } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
        e.preventDefault();
        goTo(Math.max(0, activeIdx - 1));
      } else if (e.key === 'PageDown') {
        e.preventDefault();
        goTo(Math.min(TIME_PRESETS.length - 1, activeIdx + 5));
      } else if (e.key === 'PageUp') {
        e.preventDefault();
        goTo(Math.max(0, activeIdx - 5));
      }
    };

    return (
      <div className={'roller' + (isDial ? ' roller--dial' : '')}
           role="listbox" aria-label="좌선 시간"
           tabIndex={0} onKeyDown={onKey}>
        <div className="roller__center" aria-hidden="true"/>
        <div className="roller__mask roller__mask--top" aria-hidden="true"/>
        <div className="roller__mask roller__mask--bot" aria-hidden="true"/>
        <div className="roller__viewport" ref={vpRef} onScroll={handleScroll}>
          <div className="roller__pad"/>
          <div className="roller__pad"/>
          {TIME_PRESETS.map((p, i) => {
            const f = fmtPreset(p.m);
            const dist = Math.abs(i - activeIdx);
            const cls = 'roller__item'
              + (i === activeIdx ? ' is-active' : '')
              + (dist === 1 ? ' is-near' : '')
              + (dist === 2 ? ' is-mid'  : '')
              + (dist >= 3 ? ' is-far'  : '');
            return (
              <button key={p.m}
                type="button"
                className={cls}
                role="option"
                aria-selected={i === activeIdx}
                onClick={() => goTo(i)}>
                <span className="roller__num">{f.num}</span>
                <span className="roller__unit">{f.unit}</span>
              </button>
            );
          })}
          <div className="roller__pad"/>
          <div className="roller__pad"/>
        </div>
      </div>
    );
  };

  /* =========================================================
     컴포넌트 — 메인
     ========================================================= */
  const Meditate = ({ onGoHome }) => {
    const [phase, setPhase] = React.useState('setup');     // setup | running | done
    const [minutes, setMinutes] = React.useState(5);
    const [sound, setSound] = React.useState('water');
    const [remaining, setRemaining] = React.useState(5 * 60); // seconds
    const [muted, setMuted] = React.useState(false);
    const tickRef = React.useRef(null);
    const endTimeRef = React.useRef(0);

    /* ---------- 시간 표시 ---------- */
    const total = minutes * 60;
    const fmtTime = (s) => {
      const mm = Math.floor(s / 60);
      const ss = s % 60;
      return [String(mm).padStart(2, '0'), String(ss).padStart(2, '0')];
    };

    /* ---------- 진행 링 ---------- */
    const R = 152;
    const C = 2 * Math.PI * R;
    const progress = phase === 'running'
      ? Math.max(0, Math.min(1, remaining / total))
      : phase === 'done' ? 0 : 1;
    const dashOffset = C * (1 - progress);

    /* ---------- 타이머 루프 ---------- */
    React.useEffect(() => {
      if (phase !== 'running') {
        if (tickRef.current) { clearInterval(tickRef.current); tickRef.current = null; }
        return;
      }
      tickRef.current = setInterval(() => {
        const left = Math.max(0, Math.round((endTimeRef.current - Date.now()) / 1000));
        setRemaining(left);
        if (left <= 0) {
          clearInterval(tickRef.current); tickRef.current = null;
          /* 종료 종 — 무음/뮤트가 아닌 경우에만 */
          if (sound !== 'silence' && !muted) {
            stopAll();
            ringBell(0, 196);
            ringBell(0.6, 261.6);
          } else {
            stopAll();
          }
          setPhase('done');
        }
      }, 250);
      return () => { if (tickRef.current) { clearInterval(tickRef.current); tickRef.current = null; } };
    }, [phase, sound, muted]);

    /* ---------- 라이프사이클 — 페이지 이탈 시 소리 정리 ---------- */
    React.useEffect(() => {
      return () => stopAll();
    }, []);

    /* ---------- 액션 ---------- */
    const handleStart = () => {
      const ctx = ensureCtx();
      if (ctx && ctx.state === 'suspended') ctx.resume();
      setRemaining(minutes * 60);
      endTimeRef.current = Date.now() + minutes * 60 * 1000;
      setPhase('running');
      if (!muted) {
        /* 시작 종 — 무음이 아닌 모든 모드 */
        if (sound !== 'silence') {
          ringBell(0, 196);
        }
        /* 자연음 ambient — fire / water / wind / earth */
        if (sound === 'fire' || sound === 'water' || sound === 'wind' || sound === 'earth') {
          /* 시작 종이 끝난 뒤에 ambient 페이드인 */
          setTimeout(() => startAmbient(sound), 800);
        }
      }
    };

    const handleEnd = () => {
      stopAll();
      try { audioCtx && audioCtx.state === 'running' && null; } catch(_){}
      setPhase('done');
      setRemaining(0);
    };

    const handleAgain = () => {
      setPhase('setup');
      setRemaining(minutes * 60);
    };

    const handleMute = () => {
      const next = !muted;
      setMuted(next);
      if (next) stopAll();
      else if (phase === 'running'
        && (sound === 'fire' || sound === 'water' || sound === 'wind' || sound === 'earth')) {
        startAmbient(sound);
      }
    };

    /* ---------- 화면 ---------- */
    const [mm, ss] = fmtTime(phase === 'setup' ? total : remaining);
    const sessionMinutes = phase === 'done'
      ? Math.max(1, minutes - Math.ceil(remaining / 60))
      : minutes;

    return (
      <main className="meditate" data-screen-label="명상">
        <div className="meditate__inner">
          {/* 머리말 */}
          <div className="meditate__head">
            <div className="meditate__head-label">명상 · MEDITATION</div>
            <div className="meditate__head-sub">
              {phase === 'setup'  && '시간과 음원을 고르고 시작하세요'}
              {phase === 'running' && '들숨, 그리고 날숨 — 자세를 잊으세요'}
              {phase === 'done'    && '수고하셨습니다'}
            </div>
          </div>

          {/* 중앙 다이얼 */}
          <div className={'dial' + (phase === 'done' ? ' dial--done' : '')}>
            <svg className="dial__ring" viewBox="0 0 320 320">
              <circle cx="160" cy="160" r={R} className="dial__ring-track"/>
              {phase !== 'done' && (
                <circle cx="160" cy="160" r={R}
                  className="dial__ring-progress"
                  strokeDasharray={C}
                  strokeDashoffset={dashOffset}/>
              )}
            </svg>
            <div className="dial__face">
              {phase === 'setup' ? (
                <React.Fragment>
                  <TimeRoller value={minutes} onChange={setMinutes} variant="dial"/>
                </React.Fragment>
              ) : phase === 'running' ? (
                <React.Fragment>
                  <div className="dial__time">
                    {mm}
                    <span className="dial__time-sec">:{ss}</span>
                  </div>
                  <div className="dial__time-sub">남은 시간</div>
                  <div className="dial__breath"/>
                </React.Fragment>
              ) : (
                <React.Fragment>
                  <div className="dial__done-zh">완료</div>
                  <div className="dial__done-sub">한 자리, 한 호흡</div>
                </React.Fragment>
              )}
            </div>
          </div>

          {/* ---------- SETUP ---------- */}
          {phase === 'setup' && (
            <React.Fragment>
              <div className="meditate__section">
                <div className="meditate__section-head">
                  <div className="meditate__section-label">Sound</div>
                  <div className="meditate__section-zh">소리 선택</div>
                </div>
                <div className="chips chips--sound">
                  {SOUND_OPTIONS.map(s => (
                    <button key={s.id}
                      className={'chip chip--sound chip--sound-' + s.id + (sound === s.id ? ' is-active' : '')}
                      onClick={() => setSound(s.id)}
                      title={`${s.kr} — ${s.desc}`}
                      aria-label={`${s.kr} — ${s.desc}`}>
                      {s.id === 'silence' ? (
                        <span className="chip__silence-text">무음</span>
                      ) : (
                        <React.Fragment>
                          <span className="chip__icon"><ElemIcon kind={s.id}/></span>
                          <span className="chip__label">
                            <span className="chip__label-kr">{s.kr}</span>
                          </span>
                        </React.Fragment>
                      )}
                    </button>
                  ))}
                </div>
              </div>

              <div className="meditate__actions">
                <button className="med-btn med-btn--primary" onClick={handleStart}>
                  시작
                </button>
              </div>
            </React.Fragment>
          )}

          {/* ---------- RUNNING ---------- */}
          {phase === 'running' && (
            <React.Fragment>
              <div className="meditate__running-meta">
                <span>음원</span>
                <b className="meditate__running-sound">
                  <span className="meditate__running-sound-icon">
                    <ElemIcon kind={sound} size={16}/>
                  </span>
                  {SOUND_OPTIONS.find(x => x.id === sound)?.kr}
                </b>
                ·
                <span>{minutes < 60 ? `${minutes}분` : `${minutes / 60}시간`}</span>
                {sound !== 'silence' && (
                  <React.Fragment>
                    <br/>
                    <button className={'meditate__running-mute' + (muted ? ' is-muted' : '')}
                      onClick={handleMute}>
                      <span className="dot"/>
                      {muted ? '소리 켜기' : '소리 끄기'}
                    </button>
                  </React.Fragment>
                )}
              </div>
              <div className="meditate__actions">
                <button className="med-btn med-btn--danger" onClick={handleEnd}>
                  끝내기
                </button>
              </div>
            </React.Fragment>
          )}

          {/* ---------- DONE ---------- */}
          {phase === 'done' && (
            <React.Fragment>
              <div className="meditate__done-summary">
                <div className="meditate__done-cell">
                  <div className="meditate__done-cell-key">Duration</div>
                  <div className="meditate__done-cell-val">
                    {sessionMinutes < 60
                      ? `${sessionMinutes} min`
                      : `${(sessionMinutes / 60).toFixed(sessionMinutes % 60 === 0 ? 0 : 1)} hr`}
                  </div>
                </div>
                <div className="meditate__done-cell">
                  <div className="meditate__done-cell-key">Sound</div>
                  <div className="meditate__done-cell-val meditate__done-cell-val-icon">
                    <ElemIcon kind={sound} size={22}/>
                  </div>
                </div>
                <div className="meditate__done-cell">
                  <div className="meditate__done-cell-key">Date</div>
                  <div className="meditate__done-cell-val">
                    {new Date().toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' })}
                  </div>
                </div>
              </div>
              <div className="meditate__actions">
                <button className="med-btn med-btn--ghost" onClick={onGoHome}>
                  도서관으로
                </button>
                <button className="med-btn med-btn--primary" onClick={handleAgain}>
                  다시 좌선
                </button>
              </div>
            </React.Fragment>
          )}
        </div>
      </main>
    );
  };

  window.Meditate = Meditate;
})();
