/* the turn - desktop dashboard. A purpose-built three-column command surface
   for wide screens (mobile keeps the PhoneFrame stack). Left: nav rail. Centre:
   the verdict (today's read + the call + heads-up + conditions + next windows).
   Right: the live verdict-coloured Walker Bay map. Everything reads from the
   same window.TURN the mobile app uses - no second data path, no demo copy. */

// NB: pull primitives off window with UNIQUE names. Top-level `const`s live in
// the shared classic-script scope, so reusing a name another redesign file
// already declared (e.g. Explore.jsx's `ESpotMarker`) is a redeclaration
// SyntaxError that kills this whole script - leaving the stale _ds_bundle
// DesktopShell in place. Hence the Dash* prefix.
const DashSpotMarker = window.TheTurnDesignSystem_583d9e.SpotMarker;

const DESKTOP_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "todayLayout": "band",
  "theme": "light"
}/*EDITMODE-END*/;

/* ---- heroPhrase: lead with the species only when we're field-confident;
   otherwise verdict + the place NAME (not its type). ---- */
function desktopHeroPhrase(call, area) {
  const speciesConfident = call.lifecycle === 'validated' || call.lifecycle === 'guide';
  const id = call.species && call.species[0];
  const sp = id ? speciesById(id) : null;
  const topSpName = sp ? (sp.common || '').toLowerCase().replace(/ \/.*/, '') : (id ? String(id).replace(/-/g, ' ') : null);
  const placeShort = area ? (area.name || '').split(/\s*\/\s*/)[0] : '';
  if (call.verdict === 'go')    return speciesConfident && topSpName ? `Go - ${topSpName}` : `Go${placeShort ? ` · ${placeShort}` : ''}`;
  if (call.verdict === 'scout') return `Worth a scout${placeShort ? ` · ${placeShort}` : ''}`;
  return 'Skip - bait day';
}

/* ---- mark placement: the engine areas carry distKm + compass bearing but no
   lat/lng, so we project them onto the schematic coast relative to Hermanus
   (the "you are here" at 18%/26%). East pushes right toward Gansbaai, distance
   pushes seaward (down). Schematic, not a nautical chart - markers are there to
   click into the real area, not to navigate by. ---- */
const DASH_BEARING_DEG = { N: 0, NNE: 22, NE: 45, ENE: 67, E: 90, ESE: 112, SE: 135, SSE: 157, S: 180, SSW: 202, SW: 225, WSW: 247, W: 270, WNW: 292, NW: 315, NNW: 337 };
function dashMarkXY(area) {
  const deg = ((DASH_BEARING_DEG[(area.bearing || 'E').toUpperCase()] ?? 90) * Math.PI) / 180;
  const d = Math.min(area.distKm || 0, 40);
  const ew = Math.sin(deg);   // E = +1, W = -1
  const ns = Math.cos(deg);   // N = +1, S = -1
  const x = Math.max(6, Math.min(93, 18 + ew * d * 1.5));
  const y = Math.max(20, Math.min(82, 32 - ns * d * 0.5 + d * 0.35));
  return { x, y };
}

/* ============================================================
   Verdict card - the centrepiece. Green border + glow on a GO.
   ============================================================ */
function DesktopVerdictCard({ call, area, standout, onArea, onSpecies }) {
  const c = VERDICT[call.verdict].color;
  const isGo = call.verdict === 'go';
  const low = call.confidence === 'low';
  const phrase = desktopHeroPhrase(call, area);
  const spId = call.species && call.species[0];
  const spName = (() => { const s = spId && speciesById(spId); return s ? (s.common || '').replace(/ \/.*/, '') : null; })();
  const placeLine = area ? `${area.name}${spName ? ` · ${spName}` : ''}` : '';
  const leadLabel = standout ? 'best play' : "where I'd start";
  // the chip already carries the method reason - pick a DIFFERENT read line so
  // the card doesn't say the same sentence twice.
  const readLine = (call.why || []).find(w => w && w !== call.method.reason);

  return (
    <div style={{
      position: 'relative', overflow: 'hidden',
      borderRadius: 'var(--r-lg)', padding: '26px 26px 22px',
      border: `2px ${low ? 'dashed' : 'solid'} ${c}`,
      background: low ? 'var(--surface)' : `color-mix(in srgb, ${c} 12%, var(--bg))`,
      boxShadow: (isGo && !low) ? 'var(--glow-go)' : 'none',
    }}>
      {/* big watermark fish */}
      <div aria-hidden="true" style={{ position: 'absolute', right: -30, bottom: -24, opacity: low ? 0.08 : 0.14, pointerEvents: 'none' }}>
        <FishIcon species={spId} size={220} color={c} />
      </div>

      {/* eyebrow: lead label + place */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14, position: 'relative' }}>
        <VerdictBadge verdict={call.verdict} size="lg" />
        <PostureBadge posture={call.posture} />
        <ConfidenceBadge lifecycle={call.lifecycle} style={{ marginLeft: 'auto' }} />
      </div>

      <div className="overline" style={{ fontSize: 10.5, marginBottom: 6, position: 'relative' }}>{leadLabel} · {area ? area.name : ''}</div>

      {/* headline */}
      <h1 style={{
        fontFamily: 'var(--font-display)', fontWeight: 700, margin: 0, position: 'relative',
        fontSize: 'clamp(34px, 3.4vw, 46px)', lineHeight: 1.02, letterSpacing: '-0.025em',
        color: isGo && !low ? 'var(--text)' : c, textWrap: 'balance',
      }}>{phrase}</h1>

      {/* window + place */}
      <div className="metric" style={{ marginTop: 14, fontSize: 17, color: isGo && !low ? c : 'var(--text)', position: 'relative' }}>{call.window}</div>
      <div style={{ fontSize: 14.5, color: 'var(--text-dim)', marginTop: 4, position: 'relative' }}>{placeLine} · {area ? `${area.distKm}km ${area.bearing}` : ''}</div>

      {/* method chip + reason */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 11, marginTop: 18, flexWrap: 'wrap', position: 'relative' }}>
        <span style={{
          display: 'inline-flex', alignItems: 'center', height: 28, padding: '0 12px',
          borderRadius: 'var(--r-pill)', border: `1.5px solid ${c}`, color: c,
          background: `color-mix(in srgb, ${c} 14%, transparent)`,
          fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 700, letterSpacing: '0.04em', whiteSpace: 'nowrap',
        }}>{PLATFORM_LABEL[call.platform.mode]} · {METHOD_LABEL[call.method.mode]}</span>
        <span style={{ flex: 1, minWidth: 180, fontSize: 14, color: 'var(--text-dim)', lineHeight: 1.35 }}>{call.method.reason}</span>
      </div>

      {/* the read */}
      {readLine && (
        <p style={{ margin: '16px 0 0', fontSize: 15, color: 'var(--text)', lineHeight: 1.45, position: 'relative', maxWidth: 520 }}>{readLine}</p>
      )}

      {/* logbook confidence */}
      {call.known && call.known[0] && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 9, marginTop: 16, position: 'relative' }}>
          <span style={{ flex: '0 0 auto', width: 18, height: 18, borderRadius: '50%', display: 'grid', placeItems: 'center', background: 'color-mix(in srgb, var(--verdict-go) 20%, var(--bg))', border: '1.5px solid var(--verdict-go)' }}>
            <svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M2.5 6.2l2.3 2.3 4.7-5" stroke="var(--verdict-go)" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" /></svg>
          </span>
          <span style={{ fontSize: 13.5, color: 'var(--verdict-go)' }}>{call.known[0]}</span>
        </div>
      )}

      {/* full rundown */}
      <button onClick={() => onArea(call.areaId)} style={{
        marginTop: 18, position: 'relative', alignSelf: 'flex-start',
        background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-label)',
        fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '.1em', textTransform: 'uppercase',
        padding: '2px 0', display: 'inline-flex', alignItems: 'center', gap: 6,
      }}>the full rundown <Icon name="chevron" size={14} /></button>
    </div>
  );
}

/* ============================================================
   Heads-up - the gate / hazard / eyes-on note, amber-framed.
   Only renders when there's something real to flag.
   ============================================================ */
function DesktopHeadsUp({ call, area }) {
  const lines = [];
  if (call.checks && call.checks.length) lines.push(...call.checks.slice(0, 2));
  // surface a mark hazard if one of the area's marks carries one
  const hazardMark = area && (area.marks || []).find(m => m.hazard);
  if (hazardMark && hazardMark.hazard) lines.push(hazardMark.hazard);
  if (!lines.length) return null;
  return (
    <div role="note" style={{
      display: 'flex', gap: 12, alignItems: 'flex-start',
      padding: '14px 16px', borderRadius: 'var(--r-md)',
      background: 'color-mix(in srgb, var(--verdict-caution) 12%, var(--surface))',
      border: '1px solid color-mix(in srgb, var(--verdict-caution) 45%, var(--border))',
    }}>
      <span style={{ flex: '0 0 auto', width: 22, height: 22, borderRadius: '50%', display: 'grid', placeItems: 'center', background: 'color-mix(in srgb, var(--verdict-caution) 22%, var(--bg))', color: 'var(--verdict-caution)', fontSize: 12, marginTop: 1 }}>▲</span>
      <div style={{ minWidth: 0 }}>
        <div className="overline" style={{ fontSize: 10, color: 'var(--verdict-caution)', marginBottom: 4 }}>heads up</div>
        {lines.map((l, i) => (
          <div key={i} style={{ fontSize: 13.5, color: 'var(--text)', lineHeight: 1.4, marginTop: i ? 5 : 0 }}>{l}</div>
        ))}
      </div>
    </div>
  );
}

/* ============================================================
   Next windows - the forward read, from the 7-day ahead.
   ============================================================ */
function DesktopNextWindows({ onArea }) {
  const T = window.TURN;
  const days = (T.ahead || []).slice(1).filter(d => d.areaShort); // skip today
  if (!days.length) return null;
  return (
    <div>
      <h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 16, color: 'var(--text)', margin: '0 0 10px', letterSpacing: '-0.01em' }}>Next windows</h2>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {days.map((d, i) => (
          <button key={i} onClick={() => d.areaId && onArea(d.areaId)} disabled={!d.areaId} style={{
            display: 'flex', alignItems: 'center', gap: 12, padding: '11px 14px', textAlign: 'left',
            background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 'var(--r-md)',
            cursor: d.areaId ? 'pointer' : 'default', color: 'var(--text)', width: '100%',
          }}>
            <VerdictDot verdict={d.verdict} size={11} />
            <span style={{ flex: '0 0 70px', fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 700, color: 'var(--text-dim)' }}>{d.label || d.day}</span>
            <span style={{ flex: 1, minWidth: 0, fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 14, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{d.areaShort}</span>
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-label)', whiteSpace: 'nowrap' }}>{d.wind}</span>
            {d.areaId && <Icon name="chevron" size={15} color="var(--text-label)" />}
          </button>
        ))}
      </div>
    </div>
  );
}

/* ============================================================
   Coast map - full-height verdict-coloured Walker Bay, with a
   live wind readout. Same SVG + markers as Explore's map.
   ============================================================ */
function DesktopCoastMap({ areas, conditions, onArea }) {
  const { useState } = React;
  const [sel, setSel] = useState(null);
  const visible = areas.filter(a => !a.gatedOut);
  const w = conditions && conditions.wind;
  return (
    <div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }}>
      <svg viewBox="0 0 390 440" preserveAspectRatio="xMidYMid slice" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}>
        <rect width="390" height="440" fill="var(--map-water)" />
        <path d="M0,150 C150,215 250,215 390,195" fill="none" stroke="var(--map-contour)" strokeWidth="1" strokeDasharray="2 7" />
        <path d="M0,215 C150,275 260,275 390,250" fill="none" stroke="var(--map-contour)" strokeWidth="1" strokeDasharray="2 7" />
        <path d="M0,108 C20,114 45,120 66,108 C90,116 110,126 133,148 C150,162 158,182 190,184 C225,186 240,180 265,186 C295,194 315,172 345,160 C365,154 380,152 390,150 L390,0 L0,0 Z" fill="var(--map-land)" />
        <path d="M0,108 C20,114 45,120 66,108 C90,116 110,126 133,148 C150,162 158,182 190,184 C225,186 240,180 265,186 C295,194 315,172 345,160 C365,154 380,152 390,150" fill="none" stroke="var(--map-coast)" strokeWidth="2.5" strokeLinecap="round" />
        <text x="22" y="50" fill="var(--map-label)" fontFamily="var(--font-mono)" fontSize="11" letterSpacing="2">HERMANUS</text>
        <text x="255" y="74" fill="var(--map-label)" fontFamily="var(--font-mono)" fontSize="11" letterSpacing="2">GANSBAAI</text>
      </svg>

      {/* you are here */}
      <div style={{ position: 'absolute', left: '18%', top: '26%', transform: 'translate(-50%,-50%)', zIndex: 1 }}>
        <span style={{ position: 'relative', display: 'block', width: 13, height: 13 }}>
          <span style={{ position: 'absolute', inset: -8, borderRadius: '50%', background: 'var(--keyline-soft)' }} />
          <span style={{ position: 'absolute', inset: 0, borderRadius: '50%', background: 'var(--keyline)', border: '2px solid var(--bg)' }} />
        </span>
      </div>

      {/* marks */}
      {visible.map(a => {
        const xy = dashMarkXY(a);
        return (
        <div key={a.id} style={{ position: 'absolute', left: xy.x + '%', top: xy.y + '%', transform: 'translate(-50%,-50%)', zIndex: sel === a.id ? 5 : 2 }}>
          <DashSpotMarker
            verdict={a.today.verdict} confidence={a.today.confidence}
            risk={a.risk && a.today.verdict === 'skip'} gate={a.gate}
            label={sel === a.id ? a.name : undefined} selected={sel === a.id} size={16}
            onClick={() => { sel === a.id ? onArea(a.id) : setSel(a.id); }}
          />
        </div>
        );
      })}

      {/* wind readout */}
      {w && (
        <div style={{ position: 'absolute', right: 16, top: 16, zIndex: 4, display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', background: 'var(--glass)', backdropFilter: 'var(--blur-bar)', WebkitBackdropFilter: 'var(--blur-bar)', border: '1px solid var(--hairline-bold)', borderRadius: 'var(--r-pill)' }}>
          <span style={{ display: 'inline-block', transform: `rotate(${w.deg}deg)`, color: 'var(--text-dim)', fontSize: 15 }}>↑</span>
          <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, fontWeight: 700, color: 'var(--text)' }}>{w.dir} {w.kmh != null ? w.kmh : w.kt}<span style={{ color: 'var(--text-label)', fontWeight: 400 }}> km/h</span></span>
        </div>
      )}

      {/* legend */}
      <div style={{ position: 'absolute', left: 16, right: 16, bottom: 16, zIndex: 4, display: 'flex', alignItems: 'center', gap: 16, padding: '11px 16px', background: 'var(--glass)', backdropFilter: 'var(--blur-bar)', WebkitBackdropFilter: 'var(--blur-bar)', border: '1px solid var(--hairline-bold)', borderRadius: 'var(--r-md)' }}>
        <MapLegend color="var(--verdict-go)" label="go" />
        <MapLegend color="var(--verdict-worth)" label="scout" />
        <MapLegend color="var(--verdict-dont)" label="skip" />
        <span style={{ marginLeft: 'auto', fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-label)', letterSpacing: '.04em' }}>{sel ? 'click again to open' : 'click a mark'}</span>
      </div>
    </div>
  );
}

/* ============================================================
   Desktop shell
   ============================================================ */
function DesktopShell() {
  const { useState, useEffect } = React;
  const [t, setTweak] = useTweaks(DESKTOP_TWEAK_DEFAULTS);
  const [tab, setTab] = useState('today');
  const [stack, setStack] = useState([]);
  const [logForm, setLogForm] = useState(undefined);
  const [toast, setToast] = useState(null);
  const [aheadOpen, setAheadOpen] = useState(true);   // 7-day forecast strip - open by default on desktop
  const [aheadActive, setAheadActive] = useState(0);

  // re-render when the engine upgrades window.TURN (demo -> live)
  const [, bump] = useState(0);
  useEffect(() => {
    const onReady = () => bump(v => v + 1);
    window.addEventListener('turn:dataReady', onReady);
    return () => window.removeEventListener('turn:dataReady', onReady);
  }, []);

  useEffect(() => { document.documentElement.dataset.theme = t.theme; }, [t.theme]);

  const top = stack[stack.length - 1];
  const push = (e) => setStack(s => [...s, e]);
  const back = () => setStack(s => s.slice(0, -1));
  const onSpecies = (id) => push({ type: 'species', id });
  const onArea = (id) => push({ type: 'area', id });
  const onCatch = (id) => push({ type: 'catch', id });
  const onNewLog = (areaId) => setLogForm(areaId ?? null);
  const fireToast = (m) => { setToast(m); clearTimeout(window.__dt); window.__dt = setTimeout(() => setToast(null), 2800); };
  const onSavedLog = (name, outcome) => { setLogForm(undefined); fireToast(outcome === 'went' ? { k: 'catch', title: `Banked · ${name}`, sub: 'good oil - that sharpens the next call' } : { k: 'blank', title: `${outcome === 'skipped' ? 'Skip' : 'Abort'} logged · ${name}`, sub: 'a donut, but it all counts' }); };

  const T = window.TURN;
  const primary = T && T.today && T.today.primary;
  const pArea = primary && areaById(primary.areaId);

  // Same four surfaces as the phone's bottom nav - nothing the mobile app has
  // should be missing here.
  const navItems = [
    { id: 'today', label: 'Today', icon: 'today' },
    { id: 'explore', label: 'Explore', icon: 'explore' },
    { id: 'conditions', label: 'Conditions', icon: 'conditions' },
    { id: 'guide', label: 'Field Guide', icon: 'guide' },
  ];

  return (
    <div style={{ position: 'fixed', inset: 0, display: 'grid', gridTemplateColumns: '232px minmax(440px, 1.05fr) minmax(360px, 1.25fr)', background: 'var(--bg)' }}>

      {/* ---------- LEFT RAIL ---------- */}
      <aside style={{ display: 'flex', flexDirection: 'column', padding: '26px 18px', borderRight: '1px solid var(--hairline-bold)', background: 'var(--surface)', minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '0 6px 4px' }}>
          <Mark size={34} />
          <span style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 21, color: 'var(--text)', letterSpacing: '-0.02em', whiteSpace: 'nowrap' }}>the <span style={{ color: 'var(--verdict-go)' }}>turn</span></span>
        </div>
        <div className="overline" style={{ fontSize: 10, padding: '0 8px', marginTop: 4, marginBottom: 26 }}>Walker Bay</div>

        <nav style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
          {navItems.map(it => {
            const active = tab === it.id;
            return (
              <button key={it.id} onClick={() => { setStack([]); setTab(it.id); }} style={{
                display: 'flex', alignItems: 'center', gap: 13, width: '100%', padding: '12px 14px', cursor: 'pointer',
                borderRadius: 'var(--r-md)', border: 'none', textAlign: 'left',
                background: active ? 'color-mix(in srgb, var(--verdict-go) 14%, var(--surface))' : 'transparent',
                color: active ? 'var(--text)' : 'var(--text-dim)',
                boxShadow: active ? 'inset 2px 0 0 var(--verdict-go)' : 'none',
              }}>
                <NavGlyph name={it.icon} size={21} />
                <span style={{ fontFamily: 'var(--font-display)', fontWeight: active ? 700 : 500, fontSize: 15.5, whiteSpace: 'nowrap' }}>{it.label}</span>
              </button>
            );
          })}
        </nav>

        <button onClick={() => onNewLog(tab === 'today' && primary ? primary.areaId : undefined)} style={{
          marginTop: 18, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 9, padding: '13px 14px',
          borderRadius: 'var(--r-md)', border: '1.5px solid var(--verdict-go)', background: 'color-mix(in srgb, var(--verdict-go) 12%, transparent)',
          color: 'var(--verdict-go)', cursor: 'pointer', fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 15,
        }}>
          <span style={{ display: 'grid', transform: 'scaleX(-1)' }}><FishIcon species="grunter" size={22} color="var(--verdict-go)" strokeWidth={2} /></span>
          Log a trip
        </button>

        <div style={{ marginTop: 'auto', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 6px' }}>
          <DesktopFreshness />
          <button onClick={() => setTweak('theme', t.theme === 'dark' ? 'light' : 'dark')} aria-label="theme" style={{ width: 34, height: 34, borderRadius: '50%', display: 'grid', placeItems: 'center', background: 'var(--surface-raised)', border: '1px solid var(--border)', color: 'var(--text-dim)', cursor: 'pointer', flex: '0 0 auto' }}>
            <Icon name={t.theme === 'dark' ? 'sun' : 'moon'} size={16} />
          </button>
        </div>
      </aside>

      {/* ---------- CENTRE ---------- */}
      <main style={{ overflowY: 'auto', overflowX: 'hidden', minWidth: 0 }}>
        <div style={{ maxWidth: 600, margin: '0 auto', padding: '30px 30px 56px' }}>
          {tab === 'today' && primary && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
              {/* header line */}
              <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 12 }}>
                <span className="overline" style={{ fontSize: 11 }}>{T.conditions.dateLine}</span>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-label)', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
                  <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--text-label)' }} />cached {T.conditions.refreshed}
                </span>
              </div>
              <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 26, color: 'var(--text)', letterSpacing: '-0.02em', lineHeight: 1.1 }}>
                {T.conditions.place}<span style={{ color: 'var(--text-dim)', fontWeight: 500 }}> · {T.conditions.tide.phase} tide</span>
              </div>

              <DataHonestyBanner />

              {/* conditions + 7-day forecast sit up top, same as the phone -
                  the read of the water, then the week ahead, then the call. */}
              <ConditionsStrip c={T.conditions} onClick={() => setTab('conditions')} />
              <AheadStrip ahead={T.ahead} open={aheadOpen} onToggle={() => setAheadOpen(o => !o)} active={aheadActive} onPick={setAheadActive} />

              {/* today's read eyebrow */}
              {T.today.read && (
                <div style={{ display: 'flex', gap: 11, alignItems: 'flex-start', padding: '13px 16px', background: 'color-mix(in srgb, var(--verdict-go) 8%, var(--surface))', border: '1px solid color-mix(in srgb, var(--verdict-go) 30%, var(--border))', borderRadius: 'var(--r-md)' }}>
                  <span style={{ flex: '0 0 auto', width: 8, height: 8, borderRadius: '50%', background: 'var(--verdict-go)', marginTop: 6 }} />
                  <div>
                    <div className="overline" style={{ fontSize: 10, marginBottom: 3 }}>today's read</div>
                    <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 16, color: 'var(--text)', lineHeight: 1.3 }}>{T.today.read}</div>
                  </div>
                </div>
              )}

              <DesktopVerdictCard call={primary} area={pArea} standout={T.today.standout} onArea={onArea} onSpecies={onSpecies} />
              <DesktopHeadsUp call={primary} area={pArea} />

              {/* level-day framing */}
              {!T.today.standout && T.today.contenders > 1 && (
                <p style={{ margin: 0, fontSize: 13, color: 'var(--text-dim)', lineHeight: 1.45 }}>
                  <span style={{ color: 'var(--text)', fontWeight: 700 }}>{T.today.contenders} marks are level today</span> — the bay shares one set of conditions, so it's close. Your read on mouth state, kelp and what you fancy makes the call.
                </p>
              )}

              <DesktopNextWindows onArea={onArea} />

              <p style={{ margin: '4px 0 0', fontSize: 11.5, color: 'var(--text-label)', lineHeight: 1.45 }}>
                A read on the conditions, not a guarantee. The sea changes fast — judge the swell and your exit yourself, never turn your back on it, and fish within your depth. If it looks dangerous, it is.
              </p>
            </div>
          )}

          {tab === 'today' && !primary && <DesktopEmpty />}
          {tab === 'explore' && <Explore onArea={onArea} onSpecies={onSpecies} onConditions={() => setTab('conditions')} />}
          {tab === 'conditions' && React.createElement(window.Conditions)}
          {tab === 'guide' && <FieldGuide onSpecies={onSpecies} />}
        </div>
      </main>

      {/* ---------- RIGHT: MAP ---------- */}
      <section style={{ position: 'relative', borderLeft: '1px solid var(--hairline-bold)', minWidth: 0 }}>
        {T && <DesktopCoastMap areas={T.areas} conditions={T.conditions} onArea={onArea} />}
      </section>

      {/* drill-down panel */}
      {top && (
        <div onClick={back} style={{ position: 'fixed', inset: 0, zIndex: 60, background: 'var(--scrim)', display: 'grid', placeItems: 'center', padding: 24 }}>
          <div onClick={e => e.stopPropagation()} style={{ position: 'relative', width: 'min(680px, 100%)', height: 'min(800px, 94vh)', background: 'var(--bg)', border: '1px solid var(--border-strong)', borderRadius: 'var(--r-lg)', overflow: 'hidden', boxShadow: 'var(--shadow-pop)' }}>
            {top.type === 'species' && <SpeciesPage id={top.id} onBack={back} onArea={onArea} />}
            {top.type === 'area' && <AreaDetail id={top.id} onBack={back} onSpecies={onSpecies} onArea={onArea} onLog={onCatch} onNewLog={onNewLog} />}
            {top.type === 'catch' && <CatchDetail id={top.id} onBack={back} onArea={onArea} onSpecies={onSpecies} />}
          </div>
        </div>
      )}

      {/* log form */}
      {logForm !== undefined && (
        <div style={{ position: 'fixed', inset: 0, zIndex: 70, display: 'grid', placeItems: 'center', background: 'var(--scrim)' }}>
          <div style={{ position: 'relative', width: 'min(470px, 100%)', height: 'min(820px, 96vh)' }}>
            <LogForm areaId={logForm} onClose={() => setLogForm(undefined)} onSaved={onSavedLog} />
          </div>
        </div>
      )}

      {/* toast */}
      {toast && (
        <div style={{ position: 'fixed', left: '50%', transform: 'translateX(-50%)', bottom: 28, zIndex: 80, display: 'flex', alignItems: 'center', gap: 12, padding: '14px 18px', background: 'var(--surface-raised)', border: '1px solid var(--border-strong)', borderRadius: 'var(--r-md)', boxShadow: 'var(--shadow-pop)' }}>
          <span style={{ width: 34, height: 34, borderRadius: '50%', display: 'grid', placeItems: 'center', background: toast.k === 'catch' ? 'color-mix(in srgb, var(--verdict-go) 22%, var(--bg))' : 'var(--surface)', border: `1.5px solid ${toast.k === 'catch' ? 'var(--verdict-go)' : 'var(--border-strong)'}`, color: toast.k === 'catch' ? 'var(--verdict-go)' : 'var(--text-dim)' }}>
            <Icon name={toast.k === 'catch' ? 'plus' : 'blank'} size={17} />
          </span>
          <div><div style={{ fontWeight: 700, fontSize: 15, color: 'var(--text)' }}>{toast.title}</div><div style={{ fontSize: 12, color: 'var(--text-dim)' }}>{toast.sub}</div></div>
        </div>
      )}

      <TweaksPanel>
        <TweakSection label="Today card" />
        <TweakRadio label="Layout" value={t.todayLayout} options={['band', 'instrument', 'split']} onChange={(v) => setTweak('todayLayout', v)} />
        <TweakSection label="Theme" />
        <TweakRadio label="Mode" value={t.theme} options={['dark', 'light']} onChange={(v) => setTweak('theme', v)} />
      </TweaksPanel>
    </div>
  );
}

/* freshness pip for the rail footer - mirrors the honesty flags */
function DesktopFreshness() {
  const { useState, useEffect } = React;
  const [, b] = useState(0);
  useEffect(() => { const f = () => b(v => v + 1); window.addEventListener('turn:dataReady', f); const id = setInterval(f, 60000); return () => { window.removeEventListener('turn:dataReady', f); clearInterval(id); }; }, []);
  const isReal = window.__turnDataIsReal === true;
  const err = window.__turnDataError;
  const at = window.__turnDataAt;
  const ageMin = at ? Math.round((Date.now() - at) / 60000) : null;
  const ageStr = ageMin == null ? '' : ageMin < 1 ? 'just now' : ageMin < 60 ? `${ageMin}m` : `${Math.round(ageMin / 60)}h`;
  const tone = (!isReal || err) ? 'var(--verdict-caution)' : 'var(--text-label)';
  const txt = !isReal ? 'sample data' : err ? `offline · ${ageStr}` : `cached ${ageStr}`;
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontFamily: 'var(--font-mono)', fontSize: 11, color: tone, minWidth: 0 }}>
      <span style={{ width: 6, height: 6, borderRadius: '50%', background: tone, flex: '0 0 auto' }} />
      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{txt}</span>
    </span>
  );
}

function DesktopEmpty() {
  return (
    <div style={{ display: 'grid', placeItems: 'center', minHeight: 300, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', fontSize: 13 }}>
      reading the water…
    </div>
  );
}

window.DesktopShell = DesktopShell;
