// focus.jsx — Focus (hero) screen + Nature & Duration sheets
const { useState: useStateF } = React;

// Wordmark — matches the reference: thin, widely-tracked, iridescent lavender→cyan with a soft luminous bloom.
// Wordmark — the EME logo (transparent iridescent wordmark asset).
function Wordmark({ size = 22, track = 10 }) {
  const src = window.EME_WORDMARK_SRC;
  if (src) {
    return (
      <img src={src} alt="EMEO" style={{
        height: size * 1.25, width: 'auto', objectFit: 'contain', display: 'block',
        userSelect: 'none', pointerEvents: 'none',
        filter: 'drop-shadow(0 0 14px rgba(140,180,255,.26))',
      }} />
    );
  }
  const th = useTh();
  const grad = th.name === 'dark'
    ? 'linear-gradient(100deg,#C9BCF0 0%,#F1ECFF 26%,#CDEBFF 60%,#86CBFF 100%)'
    : 'linear-gradient(100deg,#7B3FF2 0%,#5340C8 48%,#2596C9 100%)';
  return (
    <div style={{
      fontSize: size, fontWeight: 200, letterSpacing: track, paddingLeft: track,
      background: grad, WebkitBackgroundClip: 'text', backgroundClip: 'text',
      WebkitTextFillColor: 'transparent', color: 'transparent', userSelect: 'none',
      lineHeight: 1, filter: th.name === 'dark' ? 'drop-shadow(0 0 16px rgba(140,180,255,.30))' : 'none',
    }}>EMEO</div>
  );
}

// RoundCtrl — minimal control: a transparent circle with a violet outline. The centre
// (big) Play button carries no glow (border only); `minimal` gives a subtler, quieter
// treatment used by White Noise. The Stop button keeps a faint border glow.
function RoundCtrl({ icon, label, onClick, accent, th, big = false, minimal = false, iconColor, dim = false }) {
  const d = big ? 116 : 86;
  const border = accent[0];
  const [pressed, setPressed] = React.useState(false);
  const [flash, setFlash] = React.useState(false);
  const flashTimer = React.useRef(null);

  const down = () => setPressed(true);
  const up = () => setPressed(false);
  const handleClick = (e) => {
    setFlash(true);
    clearTimeout(flashTimer.current);
    flashTimer.current = setTimeout(() => setFlash(false), 280);   // glow response
    if (onClick) onClick(e);
  };
  React.useEffect(() => () => clearTimeout(flashTimer.current), []);

  // Liquid-glass material. Side buttons are convex near-black glass orbs; the central
  // Play button is a luminous violet glass dome. Both are built from layered radial
  // gradients, a thin neon-purple rim, a top specular highlight and interior depth.
  const baseBg = big
    ? `radial-gradient(128% 120% at 50% 28%, ${border}e6, ${border}82 40%, rgba(70,30,140,.7) 64%, rgba(24,11,50,.94))`
    : `radial-gradient(135% 124% at 50% 14%, rgba(70,58,104,.55), rgba(20,16,34,.88) 52%, rgba(7,6,16,.97))`;

  // Crisp neon rim + a soft NEUTRAL drop shadow for depth. No glossy streaks or shine —
  // the surface is lit only by a broad, soft, diffuse ambient light. Dimmed during a
  // running session so the timer remains the focal point.
  const rimA = dim ? (big ? '9c' : '85') : (big ? 'ff' : 'd9');
  const ringGlow = big
    ? `0 0 0 1.6px ${border}${rimA}, 0 14px 32px rgba(0,0,0,${dim ? .42 : .56}), 0 5px 14px rgba(0,0,0,.4)`
    : `0 0 0 1.4px ${border}${rimA}, 0 10px 26px rgba(0,0,0,${dim ? .44 : .56}), 0 4px 12px rgba(0,0,0,.4)`;
  const innerDepth = big
    ? `inset 0 2px 3px rgba(255,255,255,.12), inset 0 -16px 30px rgba(12,4,32,.6), inset 0 10px 26px ${border}30`
    : `inset 0 2px 3px rgba(255,255,255,.07), inset 0 -14px 24px rgba(0,0,0,.6)`;
  // Press feedback: a brief rim brighten only (no light bloom on the Play button).
  const boxShadow = `${ringGlow}, ${innerDepth}` + (flash ? (big ? `, 0 0 0 2px ${border}` : `, 0 0 0 2px ${border}, 0 0 22px ${border}80`) : '');

  // The label is absolutely positioned below the button so the column height equals the
  // button height — that lets the parent align every button's CENTRE on one axis.
  return (
    <div style={{ position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <button onClick={handleClick} onPointerDown={down} onPointerUp={up} onPointerLeave={up} onPointerCancel={up}
        className="round-ctrl liquid-ctrl" style={{
          width: d, height: d, borderRadius: '50%', cursor: 'pointer', padding: 0, position: 'relative', overflow: 'hidden',
          background: baseBg, border: 'none', boxShadow,
          // No backdrop-filter here: the orb backgrounds are near-opaque, so the blur added
          // nothing visible — but on Android WebView a backdrop-filter on a rounded,
          // overflow-clipped element that animates its transform leaves a bright 1px edge
          // seam (the "white line" on Stop/press). Dropping it removes that artefact.
          transform: pressed ? 'scale(0.94)' : 'scale(1)',
          transition: `transform ${pressed ? '80ms cubic-bezier(.4,0,.6,1)' : '180ms cubic-bezier(0,0,.2,1)'}, box-shadow 260ms ease`,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          WebkitTapHighlightColor: 'transparent',
        }}>
        {/* soft ambient diffuse light — a broad, gentle top-down glow, no streak or shine */}
        <span aria-hidden="true" style={{ position: 'absolute', inset: 0, borderRadius: '50%', pointerEvents: 'none',
          background: big
            ? 'radial-gradient(125% 100% at 50% 2%, rgba(255,255,255,.10), rgba(255,255,255,.02) 46%, transparent 72%)'
            : 'radial-gradient(125% 100% at 50% 2%, rgba(255,255,255,.07), rgba(255,255,255,.015) 46%, transparent 72%)' }} />
        {/* Nested inner circle — every button is an outer glass boundary holding a
            concentric inner layer, so all three read as one cohesive family. */}
        <span aria-hidden="true" style={{ position: 'absolute', inset: big ? 8 : 7, borderRadius: '50%', pointerEvents: 'none',
          boxShadow: big
            ? `inset 0 0 0 1px ${border}54, inset 0 0 22px ${border}38`
            : `inset 0 0 0 1px rgba(184,140,255,.16), inset 0 0 16px rgba(0,0,0,.5)` }} />
        {/* bottom interior shadow — anchors the orb and reads as soft volume */}
        <span aria-hidden="true" style={{ position: 'absolute', inset: 0, borderRadius: '50%', pointerEvents: 'none',
          background: 'radial-gradient(78% 58% at 50% 102%, rgba(0,0,0,.5), transparent 60%)' }} />
        {/* press ripple — a soft light pulse that follows the button curvature */}
        <span aria-hidden="true" className={'lc-ripple' + (flash ? ' is-on' : '')} style={{ position: 'absolute', inset: 0, borderRadius: '50%', pointerEvents: 'none',
          background: `radial-gradient(closest-side, transparent 50%, ${big ? border + '5e' : border + '4d'} 72%, transparent 88%)` }} />
        {/* session dimmer — lowers surface brightness ~25% while running; icon stays bright */}
        {dim && <span aria-hidden="true" style={{ position: 'absolute', inset: 0, borderRadius: '50%', pointerEvents: 'none',
          background: 'rgba(3,2,8,.28)' }} />}
        <div style={{ position: 'relative', color: iconColor || '#ffffff', display: 'flex',
          filter: 'drop-shadow(0 1px 6px rgba(0,0,0,.55))' }}>{icon}</div>
      </button>
      {label && <span style={{
        position: 'absolute', top: '100%', marginTop: 13, left: '50%', transform: 'translateX(-50%)',
        fontSize: 12, letterSpacing: 2, color: 'rgba(255,255,255,.85)', fontWeight: 300, whiteSpace: 'nowrap',
      }}>{label}</span>}
    </div>
  );
}

// EmeoMark — the EMEO emblem used on the splash & share card: the luminous
// violet→blue ring with the EMEO wordmark at its heart and a single white origin-
// sphere at 12 o'clock that the ring grows from. No purple outer halo.
function EmeoMark({ size = 184, breathe = true }) {
  return (
    <LevelRing size={size} colors={['#7A2BFF', '#2D7CFF']} breathe={breathe} topSphere noAura>
      <img src={window.EME_WORDMARK_SRC} alt="EMEO" style={{
        width: size * 0.6, height: 'auto', objectFit: 'contain', display: 'block',
        userSelect: 'none', pointerEvents: 'none',
        filter: 'drop-shadow(0 0 12px rgba(140,180,255,.3))',
      }} />
    </LevelRing>
  );
}

// VolumetricFog — a slow luminous nebula suspended inside the timer circle. Several
// large, heavily-blurred violet/blue clouds drift and breathe over each other (screen
// blend) to give an abstract, layered sense of 3D depth — no particles, dots or specks.
// Clipped to the circle; fades in only while a session is active.
function VolumetricFog({ active, size = 352 }) {
  return (
    <div aria-hidden="true" className={'vfog' + (active ? ' is-on' : '')}
      style={{ position: 'absolute', left: '50%', top: '50%', width: size, height: size,
        transform: 'translate(-50%,-50%)', borderRadius: '50%', overflow: 'hidden',
        pointerEvents: 'none', zIndex: 0 }}>
      <span className="vfog-blob vfog-b1" />
      <span className="vfog-blob vfog-b2" />
      <span className="vfog-blob vfog-b3" />
      <span className="vfog-blob vfog-b4" />
      <span className="vfog-blob vfog-b5" />
    </div>
  );
}

// RingParticles — calm, organic motes drifting around the timer while a focus
// session runs. Transform/opacity-only animation (GPU-friendly, battery-light).
function RingParticles({ active, count = 14, radius = 172 }) {
  const dots = React.useMemo(() => Array.from({ length: count }).map((_, i) => {
    const a = (i / count) * Math.PI * 2 + (Math.random() - 0.5) * 0.6;
    const r = radius * (0.84 + Math.random() * 0.32);
    const x0 = Math.cos(a) * r, y0 = Math.sin(a) * r;
    const da = a + (Math.random() - 0.5) * 0.45;
    const dr = r + 8 + Math.random() * 20;
    const x1 = Math.cos(da) * dr, y1 = Math.sin(da) * dr - (6 + Math.random() * 16);
    return {
      s: (2.4 + Math.random() * 3.4).toFixed(1), d: (7 + Math.random() * 5).toFixed(1),
      dl: (-Math.random() * 9).toFixed(1), o: (0.22 + Math.random() * 0.38).toFixed(2),
      x0: x0.toFixed(1), y0: y0.toFixed(1), x1: x1.toFixed(1), y1: y1.toFixed(1),
    };
  }), [count, radius]);
  // Keep the layer mounted through a graceful exit so particles fade out instead of
  // vanishing. `vis` drives a container opacity transition; unmount only after it.
  const [mounted, setMounted] = React.useState(active);
  const [vis, setVis] = React.useState(false);
  React.useEffect(() => {
    if (active) {
      setMounted(true);
      const r = requestAnimationFrame(() => setVis(true));
      return () => cancelAnimationFrame(r);
    } else {
      setVis(false);
      const tm = setTimeout(() => setMounted(false), 1900);
      return () => clearTimeout(tm);
    }
  }, [active]);
  if (!mounted) return null;
  return (
    <div className={'ring-particles' + (vis ? ' is-visible' : '')} aria-hidden="true">
      {dots.map((p, i) => (
        <i key={i} style={{
          '--s': p.s + 'px', '--d': p.d + 's', '--dl': p.dl + 's', '--o': p.o,
          '--x0': p.x0 + 'px', '--y0': p.y0 + 'px', '--x1': p.x1 + 'px', '--y1': p.y1 + 'px',
        }} />
      ))}
    </div>
  );
}

// SessionQuote — motivational words that surface once a focus session is flowing.
// Fades in a beat after the timer starts, drifts gently between quotes, and dissolves
// when the session ends. Uses the exact same pill treatment the app shows elsewhere
// (glass pill · gradient dot · 14px/300 text) so the look is consistent throughout.
function SessionQuote({ active, quotes, accent, th }) {
  // Keep the live pool in a ref so the rotation loop never restarts when the parent
  // re-renders every second (the timer tick). The driving effect depends ONLY on
  // `active`, so it runs once per session — no thrash, no flicker.
  const poolRef = React.useRef([]);
  poolRef.current = Array.isArray(quotes) ? quotes.filter((q) => q && String(q).trim()) : [];
  const [mounted, setMounted] = React.useState(false);
  const [text, setText] = React.useState('');
  const [vis, setVis] = React.useState(false); // current-quote opacity gate

  React.useEffect(() => {
    const pool = poolRef.current;
    if (!active || pool.length === 0) {
      setVis(false);
      const tm = setTimeout(() => setMounted(false), 1000);
      return () => clearTimeout(tm);
    }
    setMounted(true);
    // shuffled, non-repeating reading order
    const order = pool.map((_, i) => i);
    for (let i = order.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [order[i], order[j]] = [order[j], order[i]]; }
    let alive = true, step = 0, tShow, tHold, tSwap;
    const show = () => {
      const p = poolRef.current;
      setText(p[order[step % order.length] % p.length] || p[0]);
      tShow = setTimeout(() => { if (alive) setVis(true); }, 40);   // next tick → CSS transition runs
      if (p.length > 1) {
        // hold ~24s, then fade out, swap, fade back in
        tHold = setTimeout(() => {
          if (!alive) return;
          setVis(false);
          tSwap = setTimeout(() => { if (!alive) return; step += 1; show(); }, 1100);
        }, 24000);
      }
    };
    // Gentle entrance ~1.4s after the timer begins — lets the ring settle first.
    const intro = setTimeout(() => { if (alive) show(); }, 1400);
    return () => { alive = false; clearTimeout(intro); clearTimeout(tShow); clearTimeout(tHold); clearTimeout(tSwap); };
  }, [active]);

  if (!mounted || !text) return null;
  return (
    <div aria-hidden={!vis} style={{
      width: '100%', textAlign: 'center', pointerEvents: 'none',
      opacity: vis ? 1 : 0, transform: vis ? 'none' : 'translateY(8px)',
      transition: 'opacity 1100ms cubic-bezier(.4,0,.2,1), transform 1100ms cubic-bezier(.4,0,.2,1)',
    }}>
      <div style={{
        display: 'inline-flex', alignItems: 'center', gap: 10, padding: '10px 18px', borderRadius: 999, maxWidth: '86%',
        position: 'relative',
        background: 'rgba(14,11,22,.6)', border: `1px solid ${accent[0]}3a`,
        boxShadow: `0 6px 20px rgba(0,0,0,.4), 0 0 16px ${accent[0]}26, inset 0 0 0 1px rgba(255,255,255,.04)`,
        backdropFilter: 'blur(14px) saturate(140%)', WebkitBackdropFilter: 'blur(14px) saturate(140%)',
      }}>
        {/* small glowing circular indicator on the left */}
        <span aria-hidden="true" style={{ flexShrink: 0, width: 7, height: 7, borderRadius: '50%',
          background: accent[0], boxShadow: `0 0 8px 1px ${accent[0]}, 0 0 3px ${accent[0]}` }} />
        <span style={{ fontSize: 15, color: th.text, opacity: .94, fontWeight: 300, textAlign: 'center', lineHeight: 1.45 }}>{text}</span>
      </div>
    </div>
  );
}

// ── Time-based greeting pill (Focus header) ─────────────────────────────────────────
// Auto-selects by the user's LOCAL time (device timezone): morning / day / afternoon /
// evening / night. Sun glyphs are amber; the moon is the app violet. Re-evaluates every
// 30s so it flips automatically when the period changes.
const _gSvg = (c, children, sw) => (
  <svg viewBox="0 0 24 24" width="48" height="48" fill="none" stroke={c} strokeWidth={sw || 1.5} strokeLinecap="round" strokeLinejoin="round">{children}</svg>
);
const GIconSun = (c) => _gSvg(c, <React.Fragment>
  <circle cx="12" cy="12" r="4.7" />
  <path d="M12 2.2v2.6M12 19.2v2.6M2.2 12h2.6M19.2 12h2.6M4.7 4.7l1.85 1.85M17.45 17.45l1.85 1.85M19.3 4.7l-1.85 1.85M6.55 17.45l-1.85 1.85" />
</React.Fragment>, 1.7);
const GIconHorizonSun = (c) => _gSvg(c, <g transform="translate(0 -1)">{/* sunrise / sunset — half-sun dome on the horizon with a 5-ray fan (matches reference) */}
  {/* rays: centre vertical + two symmetric pairs, each gapped above the dome */}
  <path d="M12 9.5V7.2M15.82 10.74l1.35-1.86M8.18 10.74l-1.35-1.86M18.11 13.78l2.16-.79M5.89 13.78l-2.16-.79" />
  {/* half-sun dome */}
  <path d="M7 16A5 5 0 0 1 17 16" />
  {/* horizon line */}
  <path d="M3 16h18" />
</g>, 1.7);
const GIconAfternoon = (c) => _gSvg(c, <React.Fragment>{/* full sun + horizon line beneath */}
  <circle cx="12" cy="10.1" r="3.9" />
  <path d="M12 2.4v1.9M3.9 10.1h1.9M18.2 10.1h1.9M6.1 4.2l1.35 1.35M17.9 4.2l-1.35 1.35M6.1 16l1.35-1.35M17.9 16l-1.35-1.35" />
  <path d="M3 19.8h18" />
</React.Fragment>, 1.7);
const GIconMoon = (c) => _gSvg(c, <React.Fragment>{/* crescent + two sparkles */}
  <path d="M20 13.4A8 8 0 1 1 10.6 4 6.3 6.3 0 0 0 20 13.4Z" />
  <path d="M18.4 3.0l.6 1.5 1.5.6-1.5.6-.6 1.5-.6-1.5-1.5-.6 1.5-.6z" fill={c} stroke="none" />
  <path d="M21.4 7.8l.35.95.95.35-.95.35-.35.95-.35-.95-.95-.35.95-.35z" fill={c} stroke="none" />
</React.Fragment>);

const GOLD = '#F3B765', GVIOLET = '#B85CFF';
const GREETINGS = {
  morning:   { color: GOLD,    icon: GIconHorizonSun, t: 'greet_morning',   s: 'greet_morning_s' },
  day:       { color: GOLD,    icon: GIconSun,        t: 'greet_day',       s: 'greet_day_s' },
  afternoon: { color: GOLD,    icon: GIconAfternoon,  t: 'greet_afternoon', s: 'greet_afternoon_s' },
  evening:   { color: GOLD,    icon: GIconHorizonSun, t: 'greet_evening',   s: 'greet_evening_s' },
  night:     { color: GVIOLET, icon: GIconMoon,       t: 'greet_night',     s: 'greet_night_s' },
};
function greetPeriod(h) {
  if (h >= 5 && h < 12) return 'morning';
  if (h >= 12 && h < 15) return 'day';
  if (h >= 15 && h < 18) return 'afternoon';
  if (h >= 18 && h < 22) return 'evening';
  return 'night'; // 22:00–04:59
}
function GreetingPill({ th, t, dir, force }) {
  const [period, setPeriod] = React.useState(() => force || greetPeriod(new Date().getHours()));
  React.useEffect(() => {
    if (force) return;
    const tick = () => { const p = greetPeriod(new Date().getHours()); setPeriod((prev) => (prev === p ? prev : p)); };
    const id = setInterval(tick, 30000);
    return () => clearInterval(id);
  }, [force]);
  const g = GREETINGS[period] || GREETINGS.day;
  const rtl = dir === 'rtl';
  return (
    <div style={{
      width: '100%', boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: 18,
      padding: '20px 24px', borderRadius: 38,
      background: 'rgba(255,255,255,.02)', border: '1px solid rgba(255,255,255,.11)',
      boxShadow: `inset 0 1px 0 rgba(255,255,255,.05), 0 6px 26px ${g.color}12`,
      backdropFilter: 'blur(10px) saturate(130%)', WebkitBackdropFilter: 'blur(10px) saturate(130%)',
    }}>
      <span style={{ flexShrink: 0, display: 'flex', filter: `drop-shadow(0 0 9px ${g.color}66)` }}>{g.icon(g.color)}</span>
      <div style={{ minWidth: 0, textAlign: rtl ? 'right' : 'left' }}>
        <div style={{ fontSize: 15, fontWeight: 500, color: 'rgba(255,255,255,.92)', textTransform: 'uppercase', letterSpacing: rtl ? 0 : '.26em', lineHeight: 1 }}>{t(g.t)}</div>
        <div style={{ fontSize: 13.5, fontWeight: 300, color: 'rgba(255,255,255,.5)', marginTop: 8, lineHeight: 1.3 }}>{t(g.s)}</div>
      </div>
    </div>
  );
}

function FocusScreen({ level, accent, anim, th, timer, soundActive, activeSoundKey, onStopSound, settings, totalHours = 0, nextHours = 0,
                       onStart, onPause, onResume, onStop, onSphereTap, onOpenNature, onOpenModes, reveal,
                       quotePool, quotesOn }) {
  const { t, dir } = useI18n();
  const activeSnd = activeSoundKey ? (SOUNDS.find((s) => s.key === activeSoundKey) || null) : null;
  const { mmss, status, progress, rate, mode = 'standard',
          pomoPhase = 'focus', pomoCycle = 1, pomoCycles = 4 } = timer;
  const isIdle = status === 'idle';
  const levelNum = Math.max(1, LEVELS.findIndex((l) => l.key === level.key) + 1); // rank number for the badge
  const cycleLabel = t('cycle_of', { n: pomoCycle, total: pomoCycles });

  const centerIcon = (status === 'running')
    ? <IconPause size={44} />
    : <IconPlay size={66} />;
  const centerAction = status === 'running' ? onPause : (status === 'paused' ? onResume : onStart);
  // The session is "immersed" for BOTH running and paused, so pausing never reverts to
  // the idle layout — controls, veil, quote and timer position all stay put.
  const immersed = !isIdle;

  // Minimal-neon palette (reference): violet → blue brand, light-gray ring outline.
  const VIOLET = '#B85CFF', BLUE = '#58B6FF';
  const neon = [VIOLET, BLUE];
  // Progression section is OFF by default for a cleaner, less-gamified screen. When off,
  // the header is just the greeting pill; when on, it also shows the "progress to next
  // rank" label + bar. The greeting pill is always visible either way.
  const showProgress = !!(settings && settings.showProgress);

  return (
    <div className={'fs-stage' + (immersed ? ' is-immersed is-active' : '')}
      style={{ height: '100%', display: 'flex', flexDirection: 'column', padding: '60px 22px 0', position: 'relative', overflow: 'hidden' }}>
      {/* deep-black base */}
      <div style={{ position: 'absolute', inset: 0, background: '#050505', zIndex: 0, pointerEvents: 'none' }} />
      {/* soft violet ambient glow behind the timer — idle only; fades out on start */}
      <div className="fs-glow" style={{ position: 'absolute', left: '50%', top: '45%', transform: 'translate(-50%,-50%)',
        width: 600, height: 600, borderRadius: '50%', zIndex: 0, pointerEvents: 'none',
        background: `radial-gradient(closest-side, ${VIOLET}2b, ${VIOLET}10 44%, transparent 72%)` }} />
      {/* immersion veil — gently deepens the black during a session */}
      <div className="fs-veil" style={{ position: 'absolute', inset: 0, pointerEvents: 'none', zIndex: 3,
        background: 'radial-gradient(64% 48% at 50% 45%, rgba(5,4,10,.2) 0%, #030208 60%, #010103 100%)' }} />
      {/* Header — a time-based greeting pill, then a clean "progress to next rank" label
          above the bar. No rank badge / level number: simpler, less gamified. */}
      <div className="fs-secondary" style={{ position: 'relative', zIndex: 2, marginTop: 26 }}>
        <GreetingPill th={th} t={t} dir={dir} />
        {/* White-noise active indicator — discreet, tap to stop without opening the panel */}
        {activeSnd && (
          <div style={{ display: 'flex', justifyContent: 'center', marginTop: 14 }}>
            <button onClick={onStopSound} data-haptic="medium" aria-label={t('stop')} style={{
              display: 'inline-flex', alignItems: 'center', gap: 8, padding: '7px 8px 7px 13px', borderRadius: 99,
              background: 'rgba(255,255,255,.05)', border: '1px solid rgba(255,255,255,.12)', color: th.dim,
              cursor: 'pointer', fontFamily: 'inherit', fontSize: 13, backdropFilter: 'blur(8px)',
            }}>
              <span style={{ color: accent[0], display: 'flex' }}><IconWave size={15} /></span>
              <span>{t(activeSnd.t)}</span>
              <span style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 18, height: 18, borderRadius: '50%', background: 'rgba(255,255,255,.08)', color: th.faint }}><IconClose size={11} /></span>
            </button>
          </div>
        )}
        {showProgress && (() => {
          const nextLv = LEVELS[levelNum]; // next tier (levelNum is current rank number)
          const c = level.glow;
          const pct = nextLv ? Math.max(2, Math.min(100, Math.round((totalHours / nextLv.hours) * 100))) : 100;
          return (
            <div style={{ marginTop: 30, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 9 }}>
              {nextLv ? (
                <React.Fragment>
                  <div style={{ fontSize: 12, letterSpacing: .2, color: th.faint }}>
                    {t('progress_to', { name: nextLv.name })} · <span style={{ color: th.dim }}>{Math.floor(totalHours)} / {nextLv.hours} h</span>
                  </div>
                  <div style={{ width: 168, height: 3, borderRadius: 3, background: th.track, overflow: 'hidden' }}>
                    <div style={{ width: pct + '%', height: '100%', borderRadius: 3,
                      background: `linear-gradient(90deg, ${c}66, ${c})`, boxShadow: `0 0 4px ${c}33`,
                      transition: 'width .7s cubic-bezier(.2,.8,.2,1)' }} />
                  </div>
                </React.Fragment>
              ) : (
                <div style={{ fontSize: 11, letterSpacing: 1.4, textTransform: 'uppercase', color: c, fontWeight: 500 }}>{t('max_rank')}</div>
              )}
            </div>
          );
        })()}
      </div>

      {/* top spacer — keeps the circle vertically where it is (idle sits a touch lower) */}
      <div style={{ flex: 0.74 }} aria-hidden="true" />

      {/* Timer ring — empty circle, soft gray outline; tap (idle) to choose a mode */}
      <div onClick={isIdle ? onOpenModes : (immersed ? centerAction : undefined)}
        style={{ flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
          position: 'relative', zIndex: 4, cursor: (isIdle || immersed) ? 'pointer' : 'default' }}>
        <div key={reveal || 0} className="ring-reveal">
          <div style={{ position: 'relative', transition: 'transform 900ms cubic-bezier(.4,0,.2,1)', willChange: 'transform',
            transform: immersed ? 'translateY(-40px)' : 'translateY(-16px)' }}>
            {/* Volumetric fog — sized just inside the grey circle (R=41%≈289px) and held
                in the sphere wrapper so it travels with the ring and stays clipped to it. */}
            <VolumetricFog active={immersed} size={284} />
            <ProgressRing progress={progress} size={352} colors={neon} running={status === 'running'} rate={rate} anim={anim}
              topGlow trackColor="rgba(198,198,206,.34)" trackWidth={0.5}>
              <div style={{
                fontSize: 82, fontWeight: 200, letterSpacing: 1, lineHeight: 1, color: '#ffffff',
                display: 'flex', justifyContent: 'center', alignItems: 'baseline', gap: 4, pointerEvents: 'none',
                transform: 'translateY(-10px)' }}>
                <span>{mmss.m}</span>
                <span style={{ color: VIOLET, position: 'relative', top: -3 }}>:</span>
                <span>{mmss.s}</span>
              </div>
              {(() => {
                const running = status === 'running';
                let sub = null;
                if (!isIdle) {
                  // Mode-specific status label that also reflects running vs paused.
                  if (mode === 'pomodoro') {
                    if (pomoPhase === 'break') sub = t('pomo_break') + '\n' + cycleLabel;
                    else sub = running ? t('pomodoro_in_progress') : t('pomodoro_paused');
                  } else if (mode === 'countup') {
                    sub = running ? t('tracking_in_progress') : t('tracking_paused');
                  } else {
                    sub = running ? t('focus_in_progress') : t('focus_paused');
                  }
                } else {
                  sub = t('touch_anywhere_to_edit');
                }
                // The sublabel is absolutely positioned a fixed distance below the ring's
                // centre, so its text/height never nudges the digits — and it fades softly
                // when the label changes (running ↔ paused).
                return sub ? (
                  <div key={sub} className="ring-sub" style={{ position: 'absolute', left: 0, right: 0, top: 'calc(50% + 38px)',
                    fontSize: 12, letterSpacing: 3.5, textTransform: 'uppercase', color: 'rgba(255,255,255,.5)',
                    fontWeight: 300, textAlign: 'center', pointerEvents: 'none',
                    whiteSpace: 'pre-line', lineHeight: 1.7 }}>{sub}</div>
                ) : null;
              })()}
            </ProgressRing>
          </div>
        </div>
      </div>

      {/* lower band — the three controls, centered between the circle and the nav with
          equal spacing. The in-session quote fades in just above them once running. */}
      <div style={{ flex: 1.5, position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center',
        paddingBottom: 58, zIndex: 4 }}>
        <div style={{ position: 'absolute', top: -38, left: 0, right: 0, display: 'flex', justifyContent: 'center', pointerEvents: 'none' }}>
          <SessionQuote active={immersed && quotesOn !== false} quotes={quotePool} accent={accent} th={th} />
        </div>
        <div className="fs-controls" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 34 }}>
          <RoundCtrl th={th} icon={<IconLeaf size={38} />} label={t('nature')} onClick={onOpenNature} accent={neon} iconColor="#ffffff" minimal dim={immersed} />
          <RoundCtrl th={th} icon={centerIcon} onClick={centerAction} accent={neon} big iconColor="#ffffff" dim={immersed} />
          <RoundCtrl th={th} icon={<IconStop size={37} />} label={t('stop')} onClick={onStop} accent={neon} iconColor="#ffffff" dim={immersed} />
        </div>
      </div>
    </div>
  );
}

// ── Bottom sheet shell ────────────────────────────────────
// Tap the dimmed area (or the ✕) to dismiss — both play a smooth slide-down + fade before
// unmounting, so the picker never feels like it just vanishes (or traps the user).
function Sheet({ title, children, onClose, th }) {
  const [closing, setClosing] = React.useState(false);
  const close = () => { if (closing) return; setClosing(true); setTimeout(onClose, 250); };
  return (
    <div onClick={close} className={'sheet-backdrop' + (closing ? ' sheet-backdrop-out' : '')} style={{
      position: 'absolute', inset: 0, zIndex: 80,
      background: th.name === 'dark' ? 'rgba(4,4,8,.6)' : 'rgba(40,38,60,.34)', backdropFilter: 'blur(4px)',
      display: 'flex', alignItems: 'flex-end',
    }}>
      <div onClick={(e) => e.stopPropagation()} className={'sheet-panel' + (closing ? ' sheet-panel-out' : '')} style={{
        width: '100%', background: th.sheet,
        borderRadius: '30px 30px 0 0', border: '1px solid ' + th.border,
        borderBottom: 'none', padding: '14px 20px 40px',
        boxShadow: '0 -20px 60px rgba(0,0,0,.5)',
      }}>
        <div style={{ width: 38, height: 4, borderRadius: 99, background: th.ghost, margin: '0 auto 16px' }} />
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
          <div style={{ fontSize: 20, fontWeight: 400, color: th.text }}>{title}</div>
          <button onClick={close} style={{ background: th.surface2, border: 'none', width: 32, height: 32, borderRadius: '50%', color: th.text, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}><IconClose size={16} /></button>
        </div>
        {children}
      </div>
    </div>
  );
}

// ── NatureSheet — White Noise picker ──────────────────────
// A clean grid of the 12 environments. Tap to play (gapless loop); tap again to
// stop. All sounds are available, and the master volume rides above the grid.
function NatureSheet({ onClose, accent, th, activeSound, onSelect, volume, onVolume, isPremium, onLocked }) {
  const { t } = useI18n();
  const pct = Math.round(volume * 100);
  // Decode every sound the moment the picker opens so each preview plays instantly.
  React.useEffect(() => { try { whiteNoisePlayer.preloadAll(SOUNDS); } catch (e) {} }, []);
  return (
    <Sheet title={t('white_noise')} onClose={onClose} th={th}>
      {/* volume — labelled, with a live percentage for clear hierarchy */}
      <div style={{ padding: '0 2px 22px' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 12 }}>
          <span style={{ fontSize: 11, letterSpacing: 2, fontWeight: 500, color: th.faint, textTransform: 'uppercase' }}>{t('volume')}</span>
          <span style={{ fontSize: 12.5, color: th.dim, fontVariantNumeric: 'tabular-nums' }}>{pct}%</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <span style={{ color: th.faint, display: 'flex' }}><IconVolume size={18} /></span>
          <input type="range" min="0" max="1" step="0.01" value={volume}
            onChange={(e) => onVolume(parseFloat(e.target.value))}
            style={{ flex: 1, accentColor: accent[0], height: 4 }} />
        </div>
      </div>
      {/* ambience grid */}
      <div style={{ fontSize: 11, letterSpacing: 2, fontWeight: 500, color: th.faint, textTransform: 'uppercase', margin: '0 2px 13px' }}>{t('ambience')}</div>
      <div className="scroll-area" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12, maxHeight: 340, overflowY: 'auto', paddingBottom: 4 }}>
        {SOUNDS.map((s, i) => {
          const on = activeSound === s.key;
          const locked = !s.free && !isPremium;
          const GOLD = typeof PREMIUM_GOLD === 'string' ? PREMIUM_GOLD : '#D4A23C';
          return (
            <button key={s.key} className="wn-tile" data-haptic={locked ? 'warning' : 'medium'}
              onClick={() => (locked ? onLocked('sounds') : onSelect(s.key))} style={{
              position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 11,
              padding: '20px 8px', borderRadius: 18, cursor: 'pointer', minHeight: 104, animationDelay: (i * 0.035) + 's',
              border: '1px solid ' + (on ? accent[0] + '99' : th.border),
              background: on ? `linear-gradient(160deg, ${accent[0]}1f, ${accent[1]}0d)` : th.surface,
              color: th.text, transition: 'border-color .2s ease, background .2s ease',
            }}>
              {on && <span aria-hidden="true" style={{ position: 'absolute', top: 10, insetInlineEnd: 10, width: 6, height: 6,
                borderRadius: '50%', background: accent[0], boxShadow: `0 0 8px ${accent[0]}` }} />}
              {locked && (
                <span aria-hidden="true" title={t('premium')} style={{ position: 'absolute', top: 8, insetInlineEnd: 8,
                  width: 19, height: 19, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center',
                  background: GOLD, color: '#1c1402',
                  boxShadow: `0 2px 8px ${GOLD}66` }}>
                  <IconLock size={10} />
                </span>
              )}
              <span style={{ color: on ? accent[0] : th.dim, display: 'flex', opacity: locked ? 0.5 : 1,
                filter: on ? `drop-shadow(0 0 9px ${accent[0]}aa)` : 'none' }}>
                <SoundGlyph kind={s.key} size={26} />
              </span>
              <span style={{ fontSize: 12.5, fontWeight: 400, letterSpacing: .2, color: on ? th.text : th.dim, textAlign: 'center', opacity: locked ? 0.7 : 1 }}>{t(s.t)}</span>
            </button>
          );
        })}
      </div>
    </Sheet>
  );
}

function DurationSheet({ onClose, accent, th, presets, current, onSelect, isPremium, onLocked, favDurations = [], onSaveFav, onRemoveFav }) {
  const { t } = useI18n();
  const [custom, setCustom] = useStateF('');
  const [showCustom, setShowCustom] = useStateF(false);
  const lbl = (m) => m === 30 ? t('quick') : m === 60 ? t('standard') : t('deep');
  const isPreset = presets.includes(current);
  const applyCustom = () => {
    const v = parseInt(custom, 10);
    if (v > 0) onSelect(Math.max(CUSTOM_MIN, Math.min(CUSTOM_MAX, v)));
  };
  return (
    <Sheet title={t('session_length')} onClose={onClose} th={th}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {presets.map((m) => {
          const on = current === m;
          return (
            <button key={m} onClick={() => onSelect(m)} style={{
              display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: 'pointer',
              padding: '16px 18px', borderRadius: 16, textAlign: 'start',
              border: '1px solid ' + (on ? accent[0] + '88' : th.border),
              background: on ? `linear-gradient(135deg, ${accent[0]}22, ${accent[1]}11)` : th.surface,
              color: th.text,
            }}>
              <span style={{ fontSize: 17, fontWeight: 300 }}>{t('minutes', { m })}</span>
              {on ? <span style={{ color: accent[0], display: 'flex' }}><IconCheck size={20} /></span>
                  : <span style={{ fontSize: 13, color: th.faint }}>{lbl(m)}</span>}
            </button>
          );
        })}

        {/* Custom Duration — FREE in V3 (saving favourites/presets stays Premium) */}
        {(
          <div style={{ borderRadius: 16, border: '1px solid ' + (showCustom || !isPreset ? accent[0] + '66' : th.border), background: showCustom || !isPreset ? `linear-gradient(135deg, ${accent[0]}18, ${accent[1]}0d)` : th.surface, overflow: 'hidden' }}>
            <button onClick={() => setShowCustom((v) => !v)} style={{
              width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: 'pointer',
              padding: '16px 18px', textAlign: 'start', border: 'none', background: 'transparent', color: th.text, fontFamily: 'inherit',
            }}>
              <span>
                <span style={{ fontSize: 17, fontWeight: 300 }}>{t('custom_duration')}</span>
                {!isPreset && <span style={{ fontSize: 13, color: accent[0], marginInlineStart: 8 }}>{t('minutes', { m: current })}</span>}
              </span>
              <span style={{ color: th.faint, display: 'flex', transform: showCustom ? 'rotate(90deg)' : 'none', transition: 'transform .2s' }}><IconChevron size={18} /></span>
            </button>
            {showCustom && (
              <div style={{ padding: '0 18px 16px' }}>
                <div style={{ display: 'flex', gap: 10 }}>
                  <input type="number" min={CUSTOM_MIN} max={CUSTOM_MAX} placeholder={t('any_duration')} value={custom}
                    onChange={(e) => setCustom(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') applyCustom(); }}
                    style={{ flex: 1, minWidth: 0, padding: '14px 16px', borderRadius: 14, fontSize: 16, fontFamily: 'inherit', background: th.surface2, border: '1px solid ' + th.border2, color: th.text, outline: 'none' }} />
                  <button onClick={applyCustom} style={{ padding: '0 20px', borderRadius: 14, cursor: 'pointer', fontFamily: 'inherit', fontSize: 15, border: 'none', background: `linear-gradient(135deg,${accent[0]},${accent[1]})`, color: '#fff', fontWeight: 400 }}>{t('set')}</button>
                  <button onClick={() => { if (!isPremium) { onLocked('duration'); return; } const v = parseInt(custom, 10) || current; if (v > 0) onSaveFav(Math.max(CUSTOM_MIN, Math.min(CUSTOM_MAX, v))); }} title={t('save')} style={{ padding: '0 16px', borderRadius: 14, cursor: 'pointer', fontFamily: 'inherit', fontSize: 14, border: '1px solid ' + th.border2, background: th.surface, color: th.dim, display: 'flex', alignItems: 'center', gap: 6 }}>{!isPremium && <IconLock size={13} sw={1.7} />}{t('save')}</button>
                </div>
                {isPremium && favDurations.length > 0 && (
                  <>
                    <div style={{ fontSize: 10.5, letterSpacing: 1.6, color: th.faint, fontWeight: 500, margin: '16px 0 9px' }}>{t('saved_durations')}</div>
                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
                      {favDurations.map((m) => {
                        const on = current === m;
                        return (
                          <span key={m} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '8px 8px 8px 13px', borderRadius: 99, fontSize: 13.5,
                            border: '1px solid ' + (on ? accent[0] + '88' : th.border2), background: on ? `linear-gradient(135deg, ${accent[0]}22, ${accent[1]}11)` : th.surface, color: th.text }}>
                            <button onClick={() => onSelect(m)} style={{ background: 'none', border: 'none', color: 'inherit', font: 'inherit', cursor: 'pointer', padding: 0 }}>{m} {t('minutes_short')}</button>
                            <button onClick={() => onRemoveFav(m)} aria-label="Remove" style={{ background: th.surface2, border: 'none', width: 18, height: 18, borderRadius: '50%', color: th.faint, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}><IconClose size={10} /></button>
                          </span>
                        );
                      })}
                    </div>
                  </>
                )}
              </div>
            )}
          </div>
        )}
      </div>
    </Sheet>
  );
}

// ── Stepper — premium minus/plus duration control with the value centred ──────
// Large symmetric round glass buttons, press-scale animation, haptic on each step,
// and press-and-hold to repeat for fast adjustment.
function Stepper({ value, onChange, min = 1, max = 300, step = 5, unit, accent, th }) {
  const valRef = React.useRef(value); valRef.current = value;
  const rep = React.useRef(null);
  const clamp = (v) => Math.max(min, Math.min(max, v));
  const bump = (d, h) => {
    const nv = clamp((valRef.current || min) + d);
    if (nv !== valRef.current) { valRef.current = nv; onChange(nv); if (h) haptic(HAPTIC.medium); }
    else if (h) haptic(HAPTIC.light);
  };
  // One press = exactly one step. Press-and-HOLD (>450ms) starts a gentle auto-repeat —
  // so a normal-length tap can never register as a double input.
  const start = (d) => {
    bump(d, true);
    clearTimeout(rep.current); clearInterval(rep.current);
    rep.current = setTimeout(() => { rep.current = setInterval(() => bump(d, false), 95); }, 450);
  };
  const stop = () => { clearTimeout(rep.current); clearInterval(rep.current); rep.current = null; };
  React.useEffect(() => () => { clearTimeout(rep.current); clearInterval(rep.current); }, []);
  const Btn = ({ d, glyph }) => (
    <button type="button" data-haptic="off" className="stepper-btn"
      onPointerDown={() => start(d)} onPointerUp={stop} onPointerLeave={stop} onPointerCancel={stop}
      aria-label={glyph === '+' ? 'increase' : 'decrease'} style={{
        width: 54, height: 54, borderRadius: '50%', flexShrink: 0, cursor: 'pointer', fontFamily: 'inherit',
        display: 'flex', alignItems: 'center', justifyContent: 'center', WebkitTapHighlightColor: 'transparent',
        border: '1px solid ' + accent[0] + '55',
        background: `radial-gradient(circle at 50% 30%, ${accent[0]}24, ${th.surface2} 72%)`,
        boxShadow: `inset 0 1px 0 rgba(255,255,255,.12), 0 5px 16px rgba(0,0,0,.32)`,
        backdropFilter: 'blur(10px) saturate(140%)', WebkitBackdropFilter: 'blur(10px) saturate(140%)',
      }}>
      <span style={{ fontSize: 28, fontWeight: 300, lineHeight: 1, color: accent[0], marginTop: glyph === '+' ? -1 : 0 }}>{glyph}</span>
    </button>
  );
  return (
    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 14, padding: '2px' }}>
      <Btn d={-step} glyph="−" />
      <div style={{ flex: 1, textAlign: 'center', minWidth: 0 }}>
        <span style={{ fontSize: 34, fontWeight: 300, color: th.text, letterSpacing: .5 }}>{value}</span>
        {unit && <span style={{ fontSize: 14, color: th.faint, marginInlineStart: 6 }}>{unit}</span>}
      </div>
      <Btn d={step} glyph="+" />
    </div>
  );
}

// ── ModeSheet — timer-mode selector (opened by tapping the timer) ──────────────
// Three free liquid-glass modes; the active one is highlighted. Durations are free
// presets for everyone, while a "Custom" option is Premium (crown → paywall when free).
function ModeSheet({ onClose, accent, th, currentMode, onSelect, settings, updateSettings, isPremium, onLocked, onStart,
                     tags = [], activeTag, onSelectTag, onAddTag, onRemoveTag, focusShield, onToggleShield, onSelectShieldPreset,
                     favDurations = [], onSaveFav, onRemoveFav }) {
  const { t } = useI18n();
  const [editing, setEditing] = React.useState(null); // 'std' | 'pomo' | 'cu'
  const [v1, setV1] = React.useState('');
  const [v2, setV2] = React.useState('');
  const [newTag, setNewTag] = React.useState(null); // null = idle, string = adding
  const TAG_PALETTE = ['#B57BFF', '#56D8FF', '#7CE38B', '#F5C451', '#FF8A5C', '#FF6FB5'];
  const limit = typeof TAG_LIMIT_FREE === 'number' ? TAG_LIMIT_FREE : 3;
  const startAddTag = () => {
    if (!isPremium && tags.length >= limit) { onLocked('tags'); return; }
    setNewTag('');
  };
  const commitTag = () => {
    const name = String(newTag || '').trim();
    if (name) onAddTag(name, TAG_PALETTE[tags.length % TAG_PALETTE.length]);
    setNewTag(null);
  };
  const fs = focusShield || { enabled: false, mode: 'all' };
  const fsModeLabel = fs.mode === 'custom' ? t('shield_custom') : t('shield_block_all');
  const GOLD = typeof PREMIUM_GOLD === 'string' ? PREMIUM_GOLD : '#D4A23C';
  // Use the single shared Premium pill for consistency across the whole app.
  const PremiumTag = () => <PremiumPill />;

  const modes = [
    { key: 'standard', icon: <IconClock size={22} />,  name: 'mode_standard', sub: 'mode_standard_sub' },
    { key: 'countup',  icon: <IconChart size={22} />,  name: 'mode_count_up', sub: 'mode_count_up_sub' },
    { key: 'pomodoro', icon: <IconStreak size={22} />, name: 'mode_pomodoro', sub: 'mode_pomodoro_sub' },
  ];
  const stdPresets = [15, 30, 45, 60];
  const pomoPresets = [{ w: 25, b: 5 }, { w: 50, b: 10 }];

  const tapCustom = (which) => {
    // Custom duration is FREE in V3 (the timer is the core product).
    setEditing((e) => (e === which ? null : which));
    if (which === 'std') setV1(String(settings.stdDuration || 30));
    if (which === 'pomo') { setV1(String(settings.pomoWork || 25)); setV2(String(settings.pomoBreak || 5)); }
    if (which === 'cu') setV1(String(settings.cuGoal || 30));
  };
  const applyCustom = () => {
    const n1 = Math.max(1, Math.min(300, parseInt(v1, 10) || 0));
    if (editing === 'std') updateSettings({ stdDuration: n1 });
    else if (editing === 'pomo') updateSettings({ pomoWork: n1, pomoBreak: Math.max(1, Math.min(60, parseInt(v2, 10) || 0)) });
    else if (editing === 'cu') updateSettings({ cuGoal: n1 });
    setEditing(null);
  };

  const labelStyle = { fontSize: 11, letterSpacing: 2, fontWeight: 500, color: th.faint, textTransform: 'uppercase', margin: '0 2px 11px' };
  const rowStyle = { display: 'flex', flexWrap: 'wrap', gap: 9 };
  const inputStyle = { flex: 1, minWidth: 0, padding: '12px 14px', borderRadius: 13, fontSize: 15, fontFamily: 'inherit', background: th.surface2, border: '1px solid ' + th.border2, color: th.text, outline: 'none' };
  const Chip = ({ active, onClick, children, crown }) => (
    <button onClick={onClick} style={{
      display: 'inline-flex', alignItems: 'center', gap: 6, padding: '9px 14px', borderRadius: 13, cursor: 'pointer', fontFamily: 'inherit', fontSize: 13.5,
      border: '1px solid ' + (active ? accent[0] + 'aa' : th.border2),
      background: active ? `linear-gradient(135deg, ${accent[0]}26, ${accent[1]}12)` : th.surface,
      color: active ? th.text : th.dim,
    }}>{children}{crown && <span style={{ color: GOLD, display: 'flex' }}><IconSparkle size={11} /></span>}</button>
  );
  const setBtn = (
    <button onClick={applyCustom} style={{ padding: '0 18px', borderRadius: 13, cursor: 'pointer', fontFamily: 'inherit', fontSize: 14, border: 'none', background: `linear-gradient(135deg,${accent[0]},${accent[1]})`, color: '#fff', fontWeight: 500 }}>{t('set')}</button>
  );

  const renderCustom = () => {
    if (currentMode === 'standard') {
      const cur = settings.stdDuration || 30; const isPreset = stdPresets.includes(cur);
      const isFav = favDurations.includes(cur); const isPresetOrFav = isPreset || isFav;
      const alreadyFav = favDurations.includes(parseInt(v1, 10) || cur);
      return (<div>
        <div style={labelStyle}>{t('duration')}</div>
        <div style={rowStyle}>
          {stdPresets.map((p) => <Chip key={p} active={cur === p && editing !== 'std'} onClick={() => { setEditing(null); updateSettings({ stdDuration: p }); }}>{p} {t('minutes_short')}</Chip>)}
          {/* Saved custom durations (Premium) — tap to apply, × to remove */}
          {favDurations.map((m) => {
            const on = cur === m && editing !== 'std';
            return (
              <span key={'f' + m} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '9px 9px 9px 14px', borderRadius: 13, fontSize: 13.5,
                border: '1px solid ' + (on ? accent[0] + 'aa' : th.border2), background: on ? `linear-gradient(135deg, ${accent[0]}26, ${accent[1]}12)` : th.surface, color: on ? th.text : th.dim }}>
                <button onClick={() => { setEditing(null); updateSettings({ stdDuration: m }); }} style={{ background: 'none', border: 'none', color: 'inherit', font: 'inherit', cursor: 'pointer', padding: 0 }}>{m} {t('minutes_short')}</button>
                <button onClick={() => onRemoveFav && onRemoveFav(m)} aria-label="Remove" style={{ background: th.surface2, border: 'none', width: 18, height: 18, borderRadius: '50%', color: th.faint, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}><IconClose size={10} /></button>
              </span>
            );
          })}
          <Chip active={!isPresetOrFav || editing === 'std'} onClick={() => tapCustom('std')}>{!isPresetOrFav ? `${cur} ${t('minutes_short')}` : t('custom')}</Chip>
        </div>
        {editing === 'std' && <div className="reveal-soft" style={{ marginTop: 14, display: 'flex', flexDirection: 'column', gap: 12 }}>
          <Stepper value={parseInt(v1, 10) || 30} unit={t('minutes_short')} min={5} max={300} step={5} accent={accent} th={th}
            onChange={(v) => { setV1(String(v)); updateSettings({ stdDuration: v }); }} />
          {/* Saving a custom time is Premium — a lock communicates that to free users */}
          <button onClick={() => { if (!isPremium) { onLocked('duration'); return; } if (alreadyFav) return; const m = parseInt(v1, 10) || cur; if (m > 0 && onSaveFav) onSaveFav(m); }} disabled={isPremium && alreadyFav} style={{
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, padding: '12px', borderRadius: 13, fontFamily: 'inherit', fontSize: 14,
            cursor: (isPremium && alreadyFav) ? 'default' : 'pointer', border: '1px solid ' + accent[0] + '44',
            background: (isPremium && alreadyFav) ? th.surface : accent[0] + '14', color: (isPremium && alreadyFav) ? th.faint : accent[0],
          }}>
            {!isPremium && <IconLock size={14} sw={1.7} />}{(isPremium && alreadyFav) ? t('saved') : t('save_duration')}
          </button>
        </div>}
      </div>);
    }
    if (currentMode === 'pomodoro') {
      const cw = settings.pomoWork || 25, cb = settings.pomoBreak || 5;
      const isPreset = pomoPresets.some((p) => p.w === cw && p.b === cb);
      return (<div>
        <div style={labelStyle}>{t('focus_break')}</div>
        <div style={rowStyle}>
          {pomoPresets.map((p) => <Chip key={p.w + '/' + p.b} active={cw === p.w && cb === p.b && editing !== 'pomo'} onClick={() => { setEditing(null); updateSettings({ pomoWork: p.w, pomoBreak: p.b }); }}>{p.w} / {p.b}</Chip>)}
          <Chip active={!isPreset || editing === 'pomo'} onClick={() => tapCustom('pomo')}>{!isPreset ? `${cw} / ${cb}` : t('custom')}</Chip>
        </div>
        {editing === 'pomo' && <div className="reveal-soft" style={{ marginTop: 14, display: 'flex', flexDirection: 'column', gap: 14 }}>
          <div>
            <div style={{ ...labelStyle, margin: '0 2px 8px' }}>{t('focus_word')}</div>
            <Stepper value={parseInt(v1, 10) || 25} unit={t('minutes_short')} min={1} max={120} step={5} accent={accent} th={th}
              onChange={(v) => { setV1(String(v)); updateSettings({ pomoWork: v, pomoBreak: parseInt(v2, 10) || 5 }); }} />
          </div>
          <div>
            <div style={{ ...labelStyle, margin: '0 2px 8px' }}>{t('pomo_break')}</div>
            <Stepper value={parseInt(v2, 10) || 5} unit={t('minutes_short')} min={1} max={60} step={1} accent={accent} th={th}
              onChange={(v) => { setV2(String(v)); updateSettings({ pomoWork: parseInt(v1, 10) || 25, pomoBreak: v }); }} />
          </div>
        </div>}
      </div>);
    }
    // count-up
    const g = settings.cuGoal || 0;
    return (<div>
      <div style={labelStyle}>{t('goal')}</div>
      <div style={rowStyle}>
        <Chip active={g === 0 && editing !== 'cu'} onClick={() => { setEditing(null); updateSettings({ cuGoal: 0 }); }}>{t('no_limit')}</Chip>
        <Chip active={g > 0 || editing === 'cu'} onClick={() => tapCustom('cu')}>{g > 0 ? `${g} ${t('minutes_short')}` : t('custom')}</Chip>
      </div>
      {editing === 'cu' && <div className="reveal-soft" style={{ marginTop: 14 }}>
        <Stepper value={parseInt(v1, 10) || 30} unit={t('minutes_short')} min={5} max={300} step={5} accent={accent} th={th}
          onChange={(v) => { setV1(String(v)); updateSettings({ cuGoal: v }); }} /></div>}
    </div>);
  };

  return (
    <Sheet title={t('choose_mode')} onClose={onClose} th={th}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {modes.map((m) => {
          const on = currentMode === m.key;
          return (
            <button key={m.key} onClick={() => { setEditing(null); onSelect(m.key); }} className="mode-card" style={{
              position: 'relative', overflow: 'hidden', display: 'flex', alignItems: 'center', gap: 14, cursor: 'pointer', textAlign: 'start',
              padding: '14px 16px', borderRadius: 20, fontFamily: 'inherit', color: th.text,
              border: '1px solid ' + (on ? accent[0] + 'cc' : 'rgba(255,255,255,.10)'),
              background: on ? `linear-gradient(135deg, ${accent[0]}26, ${accent[1]}12)` : th.surface,
              boxShadow: on ? `0 0 0 1px ${accent[0]}55, 0 10px 30px ${accent[0]}22` : 'inset 0 1px 0 rgba(255,255,255,.06)',
              backdropFilter: 'blur(16px) saturate(150%)', WebkitBackdropFilter: 'blur(16px) saturate(150%)',
              transition: 'border-color .25s ease, background .25s ease, box-shadow .25s ease',
            }}>
              <span aria-hidden="true" style={{ position: 'absolute', left: 0, right: 0, top: 0, height: '48%',
                background: 'linear-gradient(180deg, rgba(255,255,255,.08), transparent)', pointerEvents: 'none' }} />
              <span style={{ width: 42, height: 42, borderRadius: 13, flexShrink: 0, position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center',
                background: `linear-gradient(140deg, ${accent[0]}33, ${accent[1]}1f)`, color: accent[0], filter: `drop-shadow(0 0 8px ${accent[0]}55)` }}>{m.icon}</span>
              <span style={{ flex: 1, minWidth: 0, position: 'relative' }}>
                <span style={{ fontSize: 16, fontWeight: 400, color: th.text }}>{t(m.name)}</span>
                <span style={{ display: 'block', fontSize: 12.5, color: th.dim, marginTop: 3 }}>{t(m.sub)}</span>
              </span>
              {on && <span style={{ color: accent[0], display: 'flex', position: 'relative', flexShrink: 0 }}><IconCheck size={20} /></span>}
            </button>
          );
        })}
      </div>
      <div style={{ height: 1, background: th.border, margin: '18px 0 16px' }} />
      {renderCustom()}

      {/* ── Project / Tags (the session inherits the active tag) ── */}
      <div style={{ height: 1, background: th.border, margin: '18px 0 16px' }} />
      <div style={labelStyle}>{t('project')}</div>
      <div style={rowStyle}>
        {tags.map((g) => {
          const on = g.id === activeTag;
          return (
            <button key={g.id} onClick={() => onSelectTag(g.id)} style={{
              display: 'inline-flex', alignItems: 'center', gap: 7, padding: '9px 13px', borderRadius: 13, cursor: 'pointer', fontFamily: 'inherit', fontSize: 13.5,
              border: '1px solid ' + (on ? accent[0] + 'aa' : th.border2),
              background: on ? `linear-gradient(135deg, ${accent[0]}26, ${accent[1]}12)` : th.surface, color: on ? th.text : th.dim,
            }}>
              <span style={{ width: 8, height: 8, borderRadius: 4, background: g.color, flexShrink: 0 }} />
              {g.label}
              {on && !g.builtin && <span onClick={(e) => { e.stopPropagation(); onRemoveTag(g.id); }} style={{ marginInlineStart: 2, color: th.faint, display: 'flex' }}><IconClose size={11} /></span>}
            </button>
          );
        })}
        {newTag === null ? (
          <button onClick={startAddTag} style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '9px 13px', borderRadius: 13, cursor: 'pointer', fontFamily: 'inherit', fontSize: 13.5, border: '1px dashed ' + th.border2, background: 'transparent', color: th.dim }}>
            {!isPremium && tags.length >= limit ? <IconLock size={12} sw={1.7} /> : <span style={{ fontSize: 16, lineHeight: 1 }}>+</span>}{t('add_tag')}
          </button>
        ) : (
          <span style={{ display: 'inline-flex', gap: 6 }}>
            <input autoFocus value={newTag} onChange={(e) => setNewTag(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') commitTag(); if (e.key === 'Escape') setNewTag(null); }} placeholder={t('tag_name')}
              style={{ width: 130, padding: '9px 12px', borderRadius: 13, fontSize: 13.5, fontFamily: 'inherit', background: th.surface2, border: '1px solid ' + accent[0] + '66', color: th.text, outline: 'none' }} />
            <button onClick={commitTag} style={{ padding: '0 14px', borderRadius: 13, cursor: 'pointer', fontFamily: 'inherit', fontSize: 13.5, border: 'none', background: `linear-gradient(135deg,${accent[0]},${accent[1]})`, color: '#fff' }}>{t('add')}</button>
          </span>
        )}
      </div>

      {/* ── Focus Shield (block distracting apps/sites during the session) ── */}
      <div style={{ height: 1, background: th.border, margin: '18px 0 16px' }} />
      <div style={{ display: 'flex', alignItems: 'center', gap: 13, padding: '13px 15px', borderRadius: 16,
        border: '1px solid ' + (fs.enabled ? accent[0] + '66' : th.border),
        background: fs.enabled ? `linear-gradient(135deg, ${accent[0]}18, ${accent[1]}0d)` : th.surface }}>
        <span style={{ color: accent[0], display: 'flex' }}><IconShield size={20} /></span>
        <span style={{ flex: 1, minWidth: 0 }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ fontSize: 15, fontWeight: 400, color: th.text }}>{t('focus_shield')}</span>
            {!isPremium && <PremiumTag />}
          </span>
          <span style={{ display: 'block', fontSize: 12, color: th.faint, marginTop: 2 }}>{fs.enabled ? fsModeLabel + ' · ' + t('shield_on') : t('focus_shield_sub')}</span>
        </span>
        <button onClick={() => { if (!isPremium) { onLocked('focus_shield'); return; } onToggleShield(!fs.enabled); }} data-haptic="medium" style={{
          width: 50, height: 30, borderRadius: 99, border: 'none', cursor: 'pointer', position: 'relative', flexShrink: 0,
          background: (fs.enabled && isPremium) ? `linear-gradient(135deg,${accent[0]},${accent[1]})` : th.surface2, transition: 'background .2s',
        }}>
          <span style={{ position: 'absolute', top: 3, left: (fs.enabled && isPremium) ? 23 : 3, width: 24, height: 24, borderRadius: '50%', background: '#fff', transition: 'left .2s', boxShadow: '0 1px 3px rgba(0,0,0,.4)' }} />
        </button>
      </div>
      {/* once the time is chosen, start the session straight from here */}
      <button onClick={onStart} data-haptic="heavy" className="prem-cta" style={{
        width: '100%', marginTop: 20, padding: '16px', borderRadius: 16, border: 'none', cursor: 'pointer', fontFamily: 'inherit',
        fontSize: 15.5, fontWeight: 500, color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 9,
        background: `linear-gradient(135deg,${accent[0]},${accent[1]})`, boxShadow: `0 12px 30px ${accent[0]}44`,
      }}><IconPlay size={17} />{t('start_focus')}</button>
    </Sheet>
  );
}

Object.assign(window, { FocusScreen, DurationSheet, ModeSheet, Sheet, Wordmark, EmeoMark, RingParticles, SessionQuote });

  