// Vapor landing — shared building blocks used by all three layout options.
// Color tokens, voice-rewritten copy, the phone mockup matching the new
// Today screen, and the two animated canvases (Word Orbit + Soul Weather
// Lissajous drift). Loops close at t=1; integer freqs only.

const VAPOR = {
  sand:      '#C7C1B1',
  sandDark:  '#B8B2A0',
  sandSoft:  '#CEC8B8',
  green:     '#2B5C3E',
  greenMid:  '#3A6B4A',
  greenSoft: '#7EA882',
  cream:     '#E8E2D9',
  ink:       '#1C1917',
  inkSoft:   '#2A2722',
  serif:     "'Cormorant Garamond', Georgia, serif",
  sans:      "'Inter', system-ui, sans-serif",
};

// ── voice-rewritten copy ─────────────────────────────────────────────
// lowercase, fragments, no marketing exhortations.
const COPY = {
  heroCycle: ['hopeful.', 'tired.', 'healing.', 'open.', 'pensive.', 'still.', 'tender.', 'lifting.'],
  heroTagline: 'your day, distilled.',
  thesis: {
    label: 'the brand',
    title: 'show up.\nthat\u2019s the whole thing.',
    body:  'a journal app for one word a day. that\u2019s the entire ask. free. no accounts. no cloud. nothing leaves your phone.',
  },
  features: [
    {
      key: 'today',
      label: 'today',
      title: 'write one word.\nclose the app.',
      body:  'that\u2019s it. the rest is there if you want it - tap your word for its meaning, long-press to share, tap your streak to see what\u2019s accumulated. but you don\u2019t have to.',
    },
    {
      key: 'calendar',
      label: 'calendar',
      title: 'your year,\nin your own handwriting.',
      body:  'every word you\u2019ve written, one square per day, set in italic. older entries fade gently the further back they go. the closest vapor gets to a diary. and even then, still just words.',
    },
    {
      key: 'words',
      label: 'words',
      title: 'the words you\nreached for most.',
      body:  'a cloud, a top ten, a 3d orbit you can share. patterns you didn\u2019t know were yours.',
    },
    {
      key: 'letters',
      label: 'letters',
      title: 'a word to your\nfuture self.',
      body:  'seal it for a week, a month, a year. opens on the date you choose. not before.',
    },
    {
      key: 'soul',
      label: 'soul weather',
      title: 'a forecast for\nyour inner climate.',
      body:  'fog burning off. frost overnight maybe. the storm not done. hand-written. no ai.',
    },
  ],
  closing: {
    tag: 'coming soon',
    title: 'your day,\ndistilled.',
    notify: 'we\u2019ll only email you about vapor.',
  },
  honest: 'vapor is a mirror. mirrors aren\u2019t therapy. crisis resources sit one tap away in settings, for thirty-plus countries, always offline.',
};

// ── shared loop clock ───────────────────────────────────────────────
// One global rotationT \u2208 [0,1] driven at 30fps; subscribers read it.
const Clock = (() => {
  const subs = new Set();
  let raf = null;
  const PERIOD_MS = 12000; // 12s loop, matches the 30fps × 12s spec
  let started = 0;
  function tick(now) {
    if (!started) started = now;
    const t = ((now - started) % PERIOD_MS) / PERIOD_MS;
    subs.forEach((fn) => fn(t));
    raf = requestAnimationFrame(tick);
  }
  function sub(fn) {
    subs.add(fn);
    if (!raf) raf = requestAnimationFrame(tick);
    return () => {
      subs.delete(fn);
      if (subs.size === 0 && raf) { cancelAnimationFrame(raf); raf = null; started = 0; }
    };
  }
  return { sub };
})();

function useT() {
  const [t, setT] = React.useState(0);
  React.useEffect(() => Clock.sub(setT), []);
  return t;
}

// ── PhoneShell ──────────────────────────────────────────────────────
// fixed-size 390×844 inner that we scale into a smaller bezel.
function PhoneShell({ scale = 0.5, children, bg = VAPOR.sand, statusColor = VAPOR.ink }) {
  const w = 390 * scale, h = 844 * scale;
  return (
    <div style={{
      width: w, height: h, borderRadius: 38 * scale, overflow: 'hidden',
      boxShadow: `0 0 0 ${0.5}px rgba(26,28,25,0.14), 0 ${28*scale}px ${64*scale}px rgba(26,28,25,0.22), 0 ${6*scale}px ${18*scale}px rgba(26,28,25,0.10)`,
      background: bg, position: 'relative',
    }}>
      <div style={{
        width: 390, height: 844, transform: `scale(${scale})`,
        transformOrigin: 'top left', position: 'relative', overflow: 'hidden',
        background: bg,
      }}>
        {/* status bar */}
        <div style={{
          position: 'absolute', top: 0, left: 0, right: 0, height: 50,
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          padding: '14px 28px', fontFamily: VAPOR.sans, fontSize: 15, fontWeight: 500,
          color: statusColor, zIndex: 10, pointerEvents: 'none',
        }}>
          <span>9:43</span>
          <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
            <span style={{ fontSize: 12 }}>•••</span>
            <span style={{ fontSize: 12 }}>•))</span>
            <span style={{ fontSize: 12 }}>▭</span>
          </div>
        </div>
        {children}
      </div>
    </div>
  );
}

// ── TodayScreen — matches the actual Today screenshot ───────────────
function TodayScreen({ word = 'grounded', note = 'feet on the floor. enough.', streak = 39, name = 'zac' }) {
  return (
    <div style={{ width: 390, height: 844, position: 'relative', background: VAPOR.sand, overflow: 'hidden' }}>
      {/* huge ghost APR / 2026 */}
      <div style={{
        position: 'absolute', left: 0, right: 0, top: 360, textAlign: 'center',
        fontFamily: VAPOR.serif, fontStyle: 'italic', fontWeight: 300, fontSize: 200,
        lineHeight: 0.9, color: 'rgba(26,28,25,0.07)', letterSpacing: '-0.02em', pointerEvents: 'none',
      }}>APR</div>
      <div style={{
        position: 'absolute', left: 0, right: 0, top: 460, textAlign: 'center',
        fontFamily: VAPOR.serif, fontStyle: 'italic', fontWeight: 300, fontSize: 150,
        lineHeight: 0.9, color: 'rgba(26,28,25,0.06)', letterSpacing: '0.04em', pointerEvents: 'none',
      }}>2026</div>

      {/* header */}
      <div style={{ position: 'absolute', top: 70, left: 0, right: 0, textAlign: 'center', zIndex: 2 }}>
        <div style={{ fontFamily: VAPOR.sans, fontSize: 12, fontWeight: 400, letterSpacing: 3, color: 'rgba(26,28,25,0.5)' }}>
          WEDNESDAY · 29 APR
        </div>
        <div style={{
          fontFamily: VAPOR.serif, fontStyle: 'italic', fontWeight: 400, fontSize: 38,
          color: VAPOR.green, marginTop: 22,
        }}>welcome, {name}.</div>
        {/* tiny weather glyph */}
        <div style={{ marginTop: 8, opacity: 0.55 }}>
          <svg width="28" height="20" viewBox="0 0 28 20" fill="none">
            <circle cx="9" cy="9" r="4.5" stroke={VAPOR.ink} strokeWidth="1"/>
            <path d="M9 2.5v1.5M9 14v1.5M2.5 9h1.5M14 9h1.5M4.5 4.5l1 1M13 13l-1-1M4.5 13l1-1M13 4.5l-1 1" stroke={VAPOR.ink} strokeWidth="0.8" strokeLinecap="round"/>
            <path d="M14 14c0-3 2.4-5.4 5.4-5.4 2.4 0 4.4 1.5 5.1 3.6.9.3 1.5 1.1 1.5 2 0 1.2-1 2.2-2.2 2.2H15.6c-1 0-1.6-.8-1.6-1.7z" fill={VAPOR.ink} fillOpacity="0.5" stroke={VAPOR.ink} strokeWidth="0.8" strokeLinejoin="round"/>
          </svg>
        </div>
      </div>

      {/* word */}
      <div style={{ position: 'absolute', top: 380, left: 0, right: 0, textAlign: 'center', zIndex: 3 }}>
        <div style={{
          fontFamily: VAPOR.serif, fontStyle: 'italic', fontWeight: 400, fontSize: 110,
          lineHeight: 1, color: VAPOR.ink, letterSpacing: '-0.02em',
        }}>{word}</div>
        <div style={{
          marginTop: 22, fontFamily: VAPOR.sans, fontWeight: 300, fontSize: 14,
          color: 'rgba(26,28,25,0.5)', fontStyle: 'italic',
        }}>{note}</div>
        <div style={{ marginTop: 10, fontFamily: VAPOR.sans, fontSize: 11, letterSpacing: 2.5, color: 'rgba(26,28,25,0.4)' }}>EDIT</div>
      </div>

      {/* streak */}
      <div style={{ position: 'absolute', bottom: 130, left: 0, right: 0, textAlign: 'center', zIndex: 3 }}>
        <div style={{ fontFamily: VAPOR.sans, fontSize: 11, fontWeight: 500, letterSpacing: 2.5, color: VAPOR.green }}>YOU&apos;VE SHOWN UP</div>
        <div style={{
          fontFamily: VAPOR.serif, fontStyle: 'italic', fontWeight: 400, fontSize: 64,
          lineHeight: 1.1, color: VAPOR.green, marginTop: 6,
        }}>{streak}</div>
        <div style={{ fontFamily: VAPOR.sans, fontSize: 11, fontWeight: 500, letterSpacing: 2.5, color: VAPOR.green, marginTop: 2 }}>TIMES IN A ROW</div>
        <div style={{ marginTop: 14, display: 'flex', justifyContent: 'center' }}>
          <div style={{
            width: 30, height: 30, borderRadius: 15, border: `0.5px solid rgba(26,28,25,0.3)`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <svg width="11" height="13" viewBox="0 0 11 13" fill="none">
              <path d="M5.5 1v8M2 4.5l3.5-3.5L9 4.5M1 11h9" stroke={VAPOR.ink} strokeOpacity="0.5" strokeWidth="0.8" strokeLinecap="round" strokeLinejoin="round"/>
            </svg>
          </div>
        </div>
      </div>

      {/* tab bar */}
      <TabBar active="today" />
    </div>
  );
}

function TabBar({ active = 'today' }) {
  const tabs = [
    { key: 'today',    label: 'Today',    icon: 'card' },
    { key: 'words',    label: 'Words',    icon: 'aa' },
    { key: 'letters',  label: 'Letters',  icon: 'mail' },
    { key: 'calendar', label: 'Calendar', icon: 'cal' },
    { key: 'settings', label: 'Settings', icon: 'gear' },
  ];
  return (
    <div style={{
      position: 'absolute', bottom: 0, left: 0, right: 0,
      borderTop: `0.5px solid rgba(26,28,25,0.1)`, padding: '14px 12px 28px',
      display: 'flex', justifyContent: 'space-around', background: 'rgba(197,192,173,0.6)',
      backdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)', zIndex: 5,
    }}>
      {tabs.map((t) => {
        const on = t.key === active;
        const c = on ? VAPOR.green : 'rgba(26,28,25,0.45)';
        return (
          <div key={t.key} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
            <TabIcon kind={t.icon} color={c} active={on} />
            <span style={{ fontFamily: VAPOR.sans, fontSize: 11, fontWeight: 400, color: c, letterSpacing: 0.2 }}>{t.label}</span>
          </div>
        );
      })}
    </div>
  );
}

function TabIcon({ kind, color, active }) {
  const sw = active ? 1.6 : 1.1;
  const props = { width: 20, height: 20, viewBox: '0 0 20 20', fill: 'none', stroke: color, strokeWidth: sw, strokeLinecap: 'round', strokeLinejoin: 'round' };
  if (kind === 'card')   return (<svg {...props}><rect x="6" y="3" width="8" height="14" rx="1.5"/><line x1="9.5" y1="6.5" x2="10.5" y2="6.5"/></svg>);
  if (kind === 'aa')     return (<svg {...props}><text x="10" y="14.5" fontFamily="Inter,sans-serif" fontSize="11" fontWeight={active ? 600 : 400} textAnchor="middle" fill={color} stroke="none">Aa</text></svg>);
  if (kind === 'mail')   return (<svg {...props}><rect x="3" y="5" width="14" height="10" rx="1.5"/><path d="M3.5 6l6.5 5 6.5-5"/></svg>);
  if (kind === 'cal')    return (<svg {...props}><rect x="3" y="4.5" width="14" height="12.5" rx="1.5"/><line x1="3" y1="8" x2="17" y2="8"/><line x1="7" y1="3" x2="7" y2="6"/><line x1="13" y1="3" x2="13" y2="6"/></svg>);
  if (kind === 'gear')   return (<svg {...props}><circle cx="10" cy="10" r="2.5"/><path d="M10 3v2M10 15v2M3 10h2M15 10h2M5 5l1.4 1.4M13.6 13.6L15 15M5 15l1.4-1.4M13.6 6.4L15 5"/></svg>);
  return null;
}

// ── WordOrbit canvas ────────────────────────────────────────────────
// 30 words on a Fibonacci sphere, single Y-axis rotation. Per-word
// depth scale 1 + zr*0.3, opacity 0.08 + ((zr+1)/2)*0.92. Body disc and
// halo gradient match the spec.
const ORBIT_WORDS = [
  'lifting','tired','healing','pensive','hopeful','still','anxious','grateful','calm',
  'brave','open','clear','grounded','tender','alive','soft','free','present','rested',
  'angry','sad','lonely','needy','shame','regret','idiotic','melancholy','unhappy','destroyed','worried',
];

function fibonacciSphere(n) {
  const pts = [];
  const phi = Math.PI * (3 - Math.sqrt(5));
  for (let i = 0; i < n; i++) {
    const y = 1 - (i / (n - 1)) * 2;
    const r = Math.sqrt(1 - y*y);
    const theta = phi * i;
    pts.push([Math.cos(theta) * r, y, Math.sin(theta) * r]);
  }
  return pts;
}

function WordOrbit({ width = 320, height = 320, words = ORBIT_WORDS, accent = VAPOR.green, ink = VAPOR.ink, halo = true }) {
  const ref = React.useRef(null);
  const t = useT();
  const pts = React.useMemo(() => fibonacciSphere(Math.min(words.length, 30)), [words.length]);

  // canvas is bigger than the visible orbit so the radial halo can fade
  // fully to transparent without the bezel clipping it. orbit radius is
  // computed off the *original* width/height; the canvas pads by 25%.
  const padFactor = 1.5;
  const cw = width * padFactor;
  const ch = height * padFactor;

  React.useEffect(() => {
    const c = ref.current;
    if (!c) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    c.width = cw * dpr; c.height = ch * dpr;
    const ctx = c.getContext('2d');
    ctx.scale(dpr, dpr);
    ctx.clearRect(0, 0, cw, ch);

    const cx = cw / 2, cy = ch / 2;
    const R = Math.min(width, height) * 0.42;
    const angle = t * Math.PI * 2;
    const cos = Math.cos(angle), sin = Math.sin(angle);

    // rotate words around Y, project orthographically
    const projected = pts.map(([x, y, z], i) => {
      const x2 = x * cos + z * sin;
      const z2 = -x * sin + z * cos;
      return { x: cx + x2 * R, y: cy + y * R, zr: z2, w: words[i % words.length] };
    });
    projected.sort((a, b) => a.zr - b.zr);

    // back hemisphere first
    function drawWord(p, frontHemi) {
      if ((p.zr >= 0) !== frontHemi) return;
      const depthScale = 1 + p.zr * 0.3;
      const op = 0.08 + ((p.zr + 1) / 2) * 0.92;
      const fontSize = 12 + Math.max(0, depthScale) * 8;
      ctx.save();
      ctx.font = `italic 400 ${fontSize}px ${VAPOR.serif}`;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      // alternate accent / ink for variety; bigger words tend to ink
      const col = (Math.abs(p.zr) > 0.55 || (p.w.length % 3 === 0)) ? ink : accent;
      ctx.fillStyle = `rgba(${hex2rgb(col)}, ${Math.min(1, op).toFixed(3)})`;
      ctx.fillText(p.w, p.x, p.y);
      ctx.restore();
    }

    projected.forEach((p) => drawWord(p, false));
    // body disc to occlude back hemisphere
    if (halo) {
      const bodyR = R * 0.81;
      const grad = ctx.createRadialGradient(cx, cy, bodyR * 0.3, cx, cy, R * 1.4);
      grad.addColorStop(0, `rgba(${hex2rgb(VAPOR.sand)}, 1)`);
      grad.addColorStop(0.42, `rgba(${hex2rgb(VAPOR.sand)}, 1)`);
      grad.addColorStop(0.6, `rgba(${hex2rgb(VAPOR.green)}, 0.18)`);
      grad.addColorStop(1, `rgba(${hex2rgb(VAPOR.green)}, 0)`);
      ctx.fillStyle = grad;
      ctx.beginPath(); ctx.arc(cx, cy, R * 1.4, 0, Math.PI * 2); ctx.fill();
      // inner solid body
      ctx.beginPath();
      ctx.fillStyle = VAPOR.sand;
      ctx.arc(cx, cy, bodyR, 0, Math.PI * 2); ctx.fill();
    }
    projected.forEach((p) => drawWord(p, true));
  }, [t, width, height, accent, ink, words, halo, pts, cw, ch]);

  return <canvas ref={ref} style={{ width: cw, height: ch, display: 'block', margin: `${-(ch - height)/2}px ${-(cw - width)/2}px` }} />;
}

function hex2rgb(hex) {
  const h = hex.replace('#', '');
  const r = parseInt(h.slice(0,2), 16), g = parseInt(h.slice(2,4), 16), b = parseInt(h.slice(4,6), 16);
  return `${r},${g},${b}`;
}

// ── SoulWeatherDrift canvas ─────────────────────────────────────────
// Up to 18 words drift on closed Lissajous paths, opacity 6–18%.
// Integer freqs in {1,2}; loops at t=1.
function SoulWeatherDrift({
  width = 320, height = 480, words = ['hopeful','ambivalent','anxious','unhappy','pensive','healing','motivated','open','tired','soft','still','clear','tender','lifting'],
  accent = VAPOR.ink, bg = 'transparent', count = 14,
}) {
  const ref = React.useRef(null);
  const t = useT();
  const seeds = React.useMemo(() => {
    const arr = [];
    for (let i = 0; i < count; i++) {
      const fx = (i % 2) + 1; // 1 or 2
      const fy = ((i + 1) % 2) + 1;
      arr.push({
        word: words[i % words.length],
        fx, fy,
        phx: (i * 0.37) % 1,
        phy: (i * 0.61) % 1,
        size: 14 + ((i * 7) % 26),
        op: 0.06 + ((i * 13) % 12) / 100,
        ax: 0.18 + ((i * 11) % 18) / 100,
        ay: 0.20 + ((i * 5) % 15) / 100,
        cx: 0.20 + ((i * 17) % 60) / 100,
        cy: 0.18 + ((i * 23) % 64) / 100,
      });
    }
    return arr;
  }, [count, words]);

  React.useEffect(() => {
    const c = ref.current; if (!c) return;
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    c.width = width * dpr; c.height = height * dpr;
    const ctx = c.getContext('2d');
    ctx.scale(dpr, dpr);
    ctx.clearRect(0, 0, width, height);
    if (bg !== 'transparent') { ctx.fillStyle = bg; ctx.fillRect(0, 0, width, height); }

    const TAU = Math.PI * 2;
    seeds.forEach((s) => {
      const x = (s.cx + s.ax * Math.sin(TAU * s.fx * t + TAU * s.phx)) * width;
      const y = (s.cy + s.ay * Math.sin(TAU * s.fy * t + TAU * s.phy)) * height;
      ctx.save();
      ctx.font = `italic 400 ${s.size}px ${VAPOR.serif}`;
      ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      ctx.fillStyle = `rgba(${hex2rgb(accent)}, ${s.op.toFixed(3)})`;
      ctx.fillText(s.word, x, y);
      ctx.restore();
    });
  }, [t, width, height, seeds, accent, bg]);

  return <canvas ref={ref} style={{ width, height, display: 'block' }} />;
}

// ── CalendarMini — tiny calendar block ─────────────────────────────
const CALENDAR_DAYS = [
  null,null,null, ['1','happy'], ['2','shame'], ['3','regret'], ['4','horrible'],
  ['5','stupid'], ['6','angry'], ['7','melancholy'], ['8','nostalgic'], ['9','selfish'], ['10','idiotic'], ['11','anxious'],
  ['12','destroyed'], ['13','angry'], ['14','fucked'], ['15','nausia'], ['16','destroyed'], ['17','sad'], ['18','lonely'],
  ['19','needy'], ['20','anxious'], ['21','struggling'], ['22','motivated'], ['23','anxious'], ['24','healing'], ['25','hopeful'],
  ['26','pensive'], ['27','ambivalent'], ['28','unhappy'], ['29','lifting', true], ['30',''], null, null,
];

function CalendarMini({ width = 320, accent = VAPOR.green }) {
  const cellH = width / 7 * 0.78;
  return (
    <div style={{ width, fontFamily: VAPOR.sans }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 14, position: 'relative' }}>
        <span style={{ color: 'rgba(26,28,25,0.4)', fontSize: 14 }}>‹</span>
        <span style={{ position: 'relative' }}>
          <span style={{
            position: 'absolute', left: '50%', top: '40%', transform: 'translate(-30%, -50%)',
            fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 56, color: 'rgba(26,28,25,0.10)', whiteSpace: 'nowrap', pointerEvents: 'none',
          }}>2026</span>
          <span style={{ fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 28, color: VAPOR.ink, position: 'relative' }}>April</span>
        </span>
        <span style={{ color: accent, fontSize: 14 }}>›</span>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', marginTop: 14, gap: 0 }}>
        {['S','M','T','W','T','F','S'].map((d, i) => (
          <div key={i} style={{ textAlign: 'center', fontSize: 9, letterSpacing: 1.5, color: 'rgba(26,28,25,0.4)', paddingBottom: 8 }}>{d}</div>
        ))}
        {CALENDAR_DAYS.map((d, i) => (
          <div key={i} style={{ height: cellH, textAlign: 'center', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
            {d ? <>
              <span style={{
                fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 16,
                color: d[2] ? accent : 'rgba(26,28,25,0.55)',
                borderBottom: d[2] ? `0.5px solid ${accent}` : 'none',
                paddingBottom: 1,
              }}>{d[0]}</span>
              <span style={{
                fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 9,
                color: d[2] ? accent : `rgba(26,28,25, ${0.18 + (i / CALENDAR_DAYS.length) * 0.32})`,
              }}>{d[1]}</span>
            </> : null}
          </div>
        ))}
      </div>
    </div>
  );
}

// ── LetterStack ────────────────────────────────────────────────────
function LetterStack({ width = 320, dark = false }) {
  const fg = dark ? VAPOR.cream : VAPOR.ink;
  const subtle = dark ? 'rgba(240,236,230,0.5)' : 'rgba(26,28,25,0.5)';
  const faint = dark ? 'rgba(240,236,230,0.25)' : 'rgba(26,28,25,0.3)';
  const cardBg = dark ? 'rgba(255,255,255,0.04)' : 'rgba(26,28,25,0.04)';
  const cardBorder = dark ? 'rgba(255,255,255,0.08)' : 'rgba(26,28,25,0.08)';
  return (
    <div style={{ width, fontFamily: VAPOR.sans, fontWeight: 300 }}>
      <div style={{ textAlign: 'center', fontSize: 10, letterSpacing: 2.5, color: subtle, fontWeight: 500 }}>NEXT TO OPEN</div>
      <div style={{ textAlign: 'center', marginTop: 18 }}>
        <span style={{ fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 64, color: fg }}>5</span>
        <span style={{ fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 26, color: fg, marginLeft: 6 }}>days</span>
      </div>
      <div style={{ textAlign: 'center', fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 16, color: subtle, marginTop: 8 }}>opens monday, 4 may</div>
      <div style={{ textAlign: 'center', fontSize: 9, letterSpacing: 2, color: faint, marginTop: 4 }}>SEALED 27 APR · 2 DAYS AGO</div>
      <div style={{ height: 1, background: cardBorder, margin: '24px 0' }} />
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <LetterRow dark={dark} icon="lock" left="opens 26 july"  right="IN 3 MONTHS" />
        <LetterRow dark={dark} icon="mail" left="tired" leftItalic right="READ YESTERDAY" accent />
        <LetterRow dark={dark} icon="lock" left="opens 24 apr 2027" right="IN 12 MONTHS" />
      </div>
      <div style={{
        marginTop: 22, padding: '14px 0', textAlign: 'center', border: `1px solid ${dark ? VAPOR.greenSoft : VAPOR.green}`,
        borderRadius: 4, color: dark ? VAPOR.cream : VAPOR.green,
      }}>
        <div style={{ fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 16 }}>write a new letter</div>
        <div style={{ fontSize: 9, letterSpacing: 2, marginTop: 2, opacity: 0.7 }}>TO YOUR FUTURE SELF</div>
      </div>
    </div>
  );
}

function LetterRow({ icon, left, right, leftItalic, accent, dark }) {
  const fg = dark ? VAPOR.cream : VAPOR.ink;
  const sub = dark ? 'rgba(240,236,230,0.45)' : 'rgba(26,28,25,0.45)';
  const acc = dark ? VAPOR.greenSoft : VAPOR.green;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
      {icon === 'lock' ? (
        <svg width="14" height="16" viewBox="0 0 14 16" fill="none" stroke={sub} strokeWidth="1"><rect x="2" y="7" width="10" height="7" rx="1"/><path d="M4 7V5a3 3 0 016 0v2"/></svg>
      ) : (
        <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke={acc} strokeWidth="1"><rect x="1.5" y="3" width="13" height="10" rx="1.5"/><path d="M2 4l6 4.5L14 4"/></svg>
      )}
      <div style={{ flex: 1, fontFamily: leftItalic ? VAPOR.serif : VAPOR.sans, fontStyle: leftItalic ? 'italic' : 'normal', fontSize: leftItalic ? 17 : 13, color: accent ? acc : fg }}>{left}</div>
      <div style={{ fontSize: 9, letterSpacing: 2, color: sub }}>{right}</div>
    </div>
  );
}

// ── SoulWeatherRetro ───────────────────────────────────────────────
const RETRO = [
  { day: 'TODAY',     phrase: 'sun returning, slowly',     word: 'open',      icon: 'sun' },
  { day: 'YESTERDAY', phrase: 'sky the same as yesterday', word: 'unhappy',   icon: 'cloud' },
  { day: 'MONDAY',    phrase: 'still but cold',            word: 'ambivalent',icon: 'rain' },
  { day: 'SUNDAY',    phrase: 'frost overnight maybe',     word: 'pensive',   icon: 'rain' },
  { day: 'SATURDAY',  phrase: 'clearer than it\u2019s been',word: 'hopeful',   icon: 'sun' },
  { day: 'FRIDAY',    phrase: 'wind off',                  word: 'healing',   icon: 'wind' },
  { day: 'THURSDAY',  phrase: 'another front',             word: 'anxious',   icon: 'storm' },
];

function WeatherIcon({ kind, color }) {
  const p = { width: 22, height: 22, viewBox: '0 0 22 22', fill: 'none', stroke: color, strokeWidth: 1.2, strokeLinecap: 'round', strokeLinejoin: 'round' };
  if (kind === 'cloud') return (<svg {...p}><path d="M5 14a3.5 3.5 0 010-7c.5-2 2.4-3.5 4.7-3.5 2.5 0 4.5 1.7 4.9 4 .2-.05.4-.07.6-.07 1.7 0 3 1.4 3 3 0 1.7-1.3 3-3 3H5z" fill={color} fillOpacity="0.85" stroke="none"/></svg>);
  if (kind === 'rain')  return (<svg {...p}><path d="M5 11a3 3 0 010-6c.4-1.7 2-3 4-3 2.1 0 3.8 1.5 4.2 3.4.2-.05.4-.07.5-.07 1.4 0 2.5 1.2 2.5 2.5 0 1.4-1.1 2.5-2.5 2.5H5z" fill={color} fillOpacity="0.85" stroke="none"/><circle cx="7" cy="15" r="0.7" fill={color}/><circle cx="11" cy="17" r="0.7" fill={color}/><circle cx="15" cy="15" r="0.7" fill={color}/><circle cx="9" cy="19" r="0.7" fill={color}/><circle cx="13" cy="19" r="0.7" fill={color}/></svg>);
  if (kind === 'sun')   return (<svg {...p}><circle cx="11" cy="11" r="4" fill={color} fillOpacity="0.85" stroke="none"/><path d="M11 2v3M11 17v3M2 11h3M17 11h3M4.5 4.5l2 2M15.5 15.5l2 2M4.5 17.5l2-2M15.5 6.5l2-2"/></svg>);
  if (kind === 'wind')  return (<svg {...p}><path d="M3 8h10a2.5 2.5 0 100-5"/><path d="M3 12h13a3 3 0 110 6"/><path d="M3 16h7"/></svg>);
  if (kind === 'storm') return (<svg {...p}><path d="M5 11a3 3 0 010-6c.4-1.7 2-3 4-3 2.1 0 3.8 1.5 4.2 3.4.2-.05.4-.07.5-.07 1.4 0 2.5 1.2 2.5 2.5 0 1.4-1.1 2.5-2.5 2.5H5z" fill={color} fillOpacity="0.85" stroke="none"/><path d="M10 12l-2 4h3l-1.5 4 4-5h-3l1.5-3z" fill={color} stroke="none"/></svg>);
  return null;
}

function SoulWeatherRetro({ width = 320, dark = false }) {
  const fg = dark ? VAPOR.cream : VAPOR.ink;
  const sub = dark ? 'rgba(240,236,230,0.5)' : 'rgba(26,28,25,0.5)';
  const accent = dark ? VAPOR.greenSoft : VAPOR.green;
  return (
    <div style={{ width, fontFamily: VAPOR.sans, fontWeight: 300 }}>
      {RETRO.map((r, i) => (
        <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '12px 0', borderTop: i ? `0.5px solid ${dark ? 'rgba(240,236,230,0.1)' : 'rgba(26,28,25,0.08)'}` : 'none' }}>
          <WeatherIcon kind={r.icon} color={accent} />
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 9, letterSpacing: 2, color: sub, fontWeight: 500 }}>{r.day}</div>
            <div style={{ fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 18, color: fg }}>{r.phrase}</div>
          </div>
          <div style={{ fontFamily: VAPOR.serif, fontStyle: 'italic', fontSize: 14, color: sub }}>{r.word}</div>
        </div>
      ))}
    </div>
  );
}

// expose globals
Object.assign(window, {
  VAPOR, COPY, useT, Clock,
  PhoneShell, TodayScreen, TabBar,
  WordOrbit, SoulWeatherDrift,
  CalendarMini, LetterStack,
  SoulWeatherRetro, WeatherIcon,
});

// ── App Store badge ───────────────────────────────────────────
const APP_STORE_URL = 'https://apps.apple.com/us/app/vapor-one-word-journal/id6764182499';
const PLAY_STORE_URL = 'https://play.google.com/store/apps/details?id=app.vaporjournal.vapor';

function AppleGlyph({ size = 16, color = 'currentColor' }) {
  return (
    <svg width={size} height={size * (27/22)} viewBox="0 0 22 27" fill={color} aria-hidden="true">
      <path d="M17.5 14.3c0-3.1 2.6-4.6 2.7-4.7-1.5-2.1-3.7-2.5-4.6-2.5-2-.2-3.8 1.1-4.8 1.1-1 0-2.5-1.1-4.2-1-2.2 0-4.2 1.2-5.3 3.2-2.3 3.9-.6 9.7 1.6 12.9 1.1 1.5 2.4 3.3 4.1 3.2 1.6-.1 2.3-1 4.2-1 1.9 0 2.5 1 4.2 1 1.7 0 2.8-1.5 3.9-3 1.2-1.7 1.7-3.4 1.7-3.5-.1 0-3.3-1.3-3.5-5.7zM14.6 4.7C15.5 3.6 16.1 2.1 16 .6c-1.3.1-2.8.9-3.7 1.9-.8.9-1.6 2.4-1.4 3.9 1.4.1 2.9-.7 3.7-1.7z"/>
    </svg>
  );
}

function AppStoreBadge({ height = 52, invert = false, style = {} }) {
  const bg = invert ? VAPOR.cream : '#0a0a0a';
  const fg = invert ? '#0a0a0a' : VAPOR.cream;
  return (
    <a href={APP_STORE_URL} target="_blank" rel="noopener noreferrer"
       style={{
         display: 'inline-flex', alignItems: 'center', gap: height * 0.22,
         height, padding: `0 ${height * 0.5}px`, borderRadius: height / 2,
         background: bg, color: fg, textDecoration: 'none',
         fontFamily: VAPOR.sans, transition: 'transform 0.25s ease, opacity 0.25s ease, box-shadow 0.25s ease',
         boxShadow: '0 1px 0 rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.30)',
         ...style,
       }}
       onMouseOver={(e)=>{ e.currentTarget.style.transform='translateY(-1px)'; }}
       onMouseOut={(e)=>{ e.currentTarget.style.transform='translateY(0)'; }}>
      <AppleGlyph size={height * 0.42} />
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', lineHeight: 1, whiteSpace: 'nowrap' }}>
        <span style={{ fontSize: height * 0.18, letterSpacing: 1, textTransform: 'uppercase', opacity: 0.7, fontWeight: 400 }}>download on the</span>
        <span style={{ fontSize: height * 0.32, fontWeight: 500, letterSpacing: 0.3, marginTop: height * 0.07 }}>App Store</span>
      </div>
    </a>
  );
}

function PlayGlyph({ size = 16 }) {
  // Google Play triangle, four-color version
  const w = size, h = size * (28/25);
  return (
    <svg width={w} height={h} viewBox="0 0 25 28" aria-hidden="true">
      <defs>
        <linearGradient id="pg-b" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stopColor="#00A1FF"/><stop offset="1" stopColor="#00E2FF"/>
        </linearGradient>
        <linearGradient id="pg-y" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0" stopColor="#FFE000"/><stop offset="1" stopColor="#FFBD00"/>
        </linearGradient>
        <linearGradient id="pg-r" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0" stopColor="#FF3A44"/><stop offset="1" stopColor="#C31162"/>
        </linearGradient>
        <linearGradient id="pg-g" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0" stopColor="#00A070"/><stop offset="1" stopColor="#3CE8B5"/>
        </linearGradient>
      </defs>
      {/* blue left wedge */}
      <path d="M0.7 0.6c-.4.4-.7 1-.7 1.8v23.2c0 .8.3 1.4.7 1.8l13.2-13.4L.7.6z" fill="url(#pg-b)"/>
      {/* yellow right wedge */}
      <path d="M18.3 18.4l-4.4-4.4 4.4-4.4 5.6 3.2c1.6.9 1.6 2.4 0 3.3l-5.6 3.3z" fill="url(#pg-y)"/>
      {/* red lower wedge */}
      <path d="M13.9 14L.7 27.4c.5.6 1.5.7 2.5.1l15.1-8.6L13.9 14z" fill="url(#pg-r)"/>
      {/* green upper wedge */}
      <path d="M13.9 14L18.3 9.6 3.2.9C2.2.4 1.2.5.7 1L13.9 14z" fill="url(#pg-g)"/>
    </svg>
  );
}

function PlayStoreBadge({ height = 52, invert = false, style = {} }) {
  const bg = invert ? VAPOR.cream : '#0a0a0a';
  const fg = invert ? '#0a0a0a' : VAPOR.cream;
  return (
    <a href={PLAY_STORE_URL} target="_blank" rel="noopener noreferrer"
       style={{
         display: 'inline-flex', alignItems: 'center', gap: height * 0.22,
         height, padding: `0 ${height * 0.5}px`, borderRadius: height / 2,
         background: bg, color: fg, textDecoration: 'none',
         fontFamily: VAPOR.sans, transition: 'transform 0.25s ease, opacity 0.25s ease, box-shadow 0.25s ease',
         boxShadow: '0 1px 0 rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.30)',
         ...style,
       }}
       onMouseOver={(e)=>{ e.currentTarget.style.transform='translateY(-1px)'; }}
       onMouseOut={(e)=>{ e.currentTarget.style.transform='translateY(0)'; }}>
      <PlayGlyph size={height * 0.46} />
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start', lineHeight: 1, whiteSpace: 'nowrap' }}>
        <span style={{ fontSize: height * 0.18, letterSpacing: 1, textTransform: 'uppercase', opacity: 0.7, fontWeight: 400 }}>get it on</span>
        <span style={{ fontSize: height * 0.32, fontWeight: 500, letterSpacing: 0.3, marginTop: height * 0.07 }}>Google Play</span>
      </div>
    </a>
  );
}

// expose new globals
Object.assign(window, { APP_STORE_URL, PLAY_STORE_URL, AppleGlyph, PlayGlyph, AppStoreBadge, PlayStoreBadge });
