  /* ============ SPOT BRAND TOKENS (brand lock v1.1) ============ */
  :root{
    --bg:#0b1118;
    --surface:rgba(19,28,38,.82);
    --surface-solid:#131c26;
    --surface-2:#1a2634;
    --line:rgba(230,238,246,.12);
    --glass-hi:rgba(255,255,255,.07);
    --text:#e8eef4;
    --text-2:#75858f;
    --accent:#e8ff3d;
    --accent-2:#4dffc8;
    --accent-ink:#20260a;
    --warn:#f0a34e;
    --off:#5c6b58;
    /* PHASE 8.4 — surface + rhythm scale.
       Before this pass the stylesheet used six horizontal gutters (10/12/14/16/
       18/20/26px) and eight card radii (8/9/11/12/13/14/16/18px), accumulated
       one screen at a time. Neither set encoded a decision; they were just the
       number that looked right that day, which is precisely why the app read as
       several UI generations. Two gutters and four radii replace them, and each
       one means something. */
    --gut:20px;        /* screen content: sections, rows, headings, body copy */
    --gut-card:14px;   /* card lists and media surfaces, whose own padding adds
                          the rest -- feed cards, decision cards, the map */
    --r-sm:10px;       /* thumbnails and small media */
    --r-chip:12px;     /* tiles, inline blocks, small controls */
    --r-md:14px;       /* secondary cards and disclosure rows */
    --r-lg:18px;       /* primary cards and full-width media */
    --radius-card:var(--r-lg);
    --radius-pill:999px;
    /* One eyebrow everywhere: SECTION LABELS were 9.5/10/10.5/11px at .1/.12/
       .14em tracking across eleven selectors. */
    --fs-eyebrow:10px;
    --track-eyebrow:.14em;
    --ease:cubic-bezier(.16,1,.3,1);
    --breath-ease:cubic-bezier(.45,.05,.55,.95);
    /* ===== PHASE 8.5 — MOTION VOCABULARY =====
       Three durations and three curves, and that is the whole system. Motion in
       SPOT exists to say where content came from, where it went, what expanded
       and what happened -- never to decorate, and never to make a surfer wait
       for an answer. Anything that cannot be described in those terms does not
       get animated.

       MICRO    presses, chip and tile selection, disclosure indicators
       STD      accordions, card expansion, sheet settle, segmented indicator
       SCREEN   navigation between screens and panel arrival

       --ease (already the app's curve) decelerates and does not overshoot; it
       is the default for almost everything. --e-settle has the faintest
       overshoot and is used ONLY where a surface should feel like it has weight
       -- the draggable sheet. --e-in is for things leaving. */
    --t-micro:130ms;
    --t-std:200ms;
    --t-screen:260ms;
    --e-settle:cubic-bezier(.22,1.02,.36,1);
    --e-in:cubic-bezier(.4,0,1,1);
    --hazard:#ff7043;
    --hazard-2:#ff5b3c;
  }
  *{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
  html,body{background:#040608}
  body{-webkit-tap-highlight-color:transparent}
  body{font-family:'Outfit',system-ui,sans-serif;color:var(--text);min-height:100dvh;display:flex;flex-direction:column;align-items:center;justify-content:center}
  /* PHASE 8.5F PILL-CENTERING ROOT CAUSE: no rule anywhere in this sheet
     ever reset -webkit-appearance on <button>. Every custom-styled button
     in the app (.chip, .seg button, .follow, .sgbtn, .dpill-adjacent
     controls, etc.) already declares its OWN background/border/padding,
     but on iOS Safari specifically, an un-reset <button> still renders
     through the OS's native control chrome underneath those declarations
     -- that chrome imposes its own internal text-baseline/padding
     behaviour that does not fully yield to CSS padding/line-height/
     align-items. This is why the earlier line-height:1 pass fixed the
     centering everywhere EXCEPT real iOS Safari: it was correctly fixing
     the CSS layer while a lower, OS-level layer kept fighting it. One
     reset, at the root every button in the app already inherits from,
     instead of a per-pill patch -- the shared cause the repeated
     line-height-only fixes were missing. `appearance:none` is the
     standard (non -webkit-prefixed) property; both are set since Safari's
     support history makes the prefixed one still the one that matters
     here. Safe for every button in the app: none of them relies on any
     native chrome today -- all already declare their own visual styling
     explicitly. */
  button{font-family:inherit;cursor:pointer;-webkit-appearance:none;appearance:none}
  img{display:block}
  .hint{display:none;color:var(--text-2);font-size:13px;margin-top:18px;gap:16px}
  .hint i{font-style:normal;color:var(--accent)}

  .phone{width:390px;height:min(844px,100dvh);background:var(--bg);border:1px solid var(--line);border-radius:44px;position:relative;overflow:hidden}
  @media (min-width:480px){ .hint{display:flex} }
  @media (max-width:479px){ body{justify-content:flex-start} .phone{width:100%;height:100dvh;border:none;border-radius:0} }

  /* ============ LOGO / WAVE SYSTEM ============ */
  .wavefast{display:none}
  .cam.recording .wavefast{display:block}
  .cam.recording .waveidle{display:none}
  .waveloader{display:none;justify-content:center;padding:6px 0 10px}
  .waveloader.show{display:flex}
  .waveloader svg{width:40px;height:24px}
  .waveloader path{fill:none;stroke:var(--accent);stroke-width:2.4;stroke-linecap:round}
  /* ============ SPLASH ============ */
  .splash{position:absolute;inset:0;z-index:100;background:var(--bg);display:flex;flex-direction:column;align-items:center;justify-content:center;transition:opacity .7s var(--ease),visibility .7s}
  .splash.gone{opacity:0;visibility:hidden}
  .sp-stage{display:flex;flex-direction:column;align-items:center;gap:26px}

  /* ============ SPOT WORDMARK (Phase 8.5B) ============
     A compact, blocky, typographic mark -- SP / OT stacked 2x2 -- replacing
     the animated wave-squiggle-and-letters mark this app shipped with since
     Phase 0. ONE mark, used at two sizes, on BOTH surfaces that carried the
     old one (the splash hero below, and the persistent header .brand) --
     deliberately, so the app is never showing two competing marks at once.

     Built as a plain CSS grid of four tiles, not an SVG or icon font: the
     small cross-shaped negative space in the centre is the grid `gap`
     itself -- the background showing through where all four tiles meet --
     not a separate cutout shape, and it holds up at any size for free.
     Monochrome by default (a solid tile colour + a contrasting glyph
     colour); the hero variant borrows the product's own accent/ink pairing
     used everywhere else a filled accent surface needs readable text (the
     same tokens a GO verdict pill uses), so the mark reads as part of this
     product's colour world without depending on colour to be legible --
     turn accent to any single flat colour, including plain black or white,
     and the mark is still exactly as readable. */
  .spot-mark{display:inline-grid;grid-template-columns:repeat(2,1fr);grid-template-rows:repeat(2,1fr);flex:0 0 auto}
  .spot-mark b{display:flex;align-items:center;justify-content:center;
    font-family:'Outfit',system-ui,sans-serif;font-weight:800;font-style:normal;line-height:1}

  /* Header brand: small, neutral (text-on-background), sits quietly beside
     nav icons rather than competing with the accent colour for attention. */
  .spot-mark--sm{width:22px;height:22px;gap:2px}
  .spot-mark--sm b{background:var(--text);color:var(--bg);font-size:9px;border-radius:4px}

  /* Splash hero: the one moment this product shows its mark at full size.
     A single restrained scale+fade entrance -- FAST/QUIET/PURPOSEFUL, the
     same motion principles as the rest of the product, not the old
     multi-stage fly-in/impact/splat/shake sequence that was choreographed
     specifically around the wave-squiggle concept this replaces. One-shot,
     plays once on mount; the pre-existing 4s splash-dismiss timeout in
     app.js is untouched and needs nothing from this mark to function. */
  .spot-mark--hero{width:168px;height:168px;gap:7px;
    animation:markIn .55s cubic-bezier(.22,1,.36,1) both}
  .spot-mark--hero b{background:var(--accent);color:var(--accent-ink);font-size:42px;border-radius:var(--r-chip)}
  @keyframes markIn{from{opacity:0;transform:scale(.85)}to{opacity:1;transform:scale(1)}}
  @media (max-width:379px){ .spot-mark--hero{width:140px;height:140px} .spot-mark--hero b{font-size:34px} }

  .splash .sp-tag{font-size:11.5px;font-weight:500;letter-spacing:.32em;text-transform:uppercase;color:var(--text-2);opacity:0;animation:tagin .6s ease .45s forwards;text-align:center;padding-left:.32em}
  @keyframes tagin{to{opacity:.9}}
  @media (prefers-reduced-motion: reduce){ .spot-mark--hero,.splash .sp-tag{animation-duration:.01s;animation-delay:0s} }

  /* ============ ROUTER ============
     PHASE 8.5: two DIFFERENT navigations, because they mean different things.

     A DETAIL PUSH (For You -> Spot View, a profile, a partner) has real
     hierarchy: the new screen comes from the right and the old one recedes, so
     going back is obviously the reverse. That is the default below, tightened
     from 380ms to the SCREEN token and given a slight fade on the receding
     screen so depth reads without a heavy shadow.

     A PRIMARY TAB SWAP (For You <-> Discover <-> Sessions) has NO hierarchy.
     Sliding those sideways implies Sessions is "further right" than Discover,
     which is a lie about the product, and it costs a full-screen composite on
     every tab press. Tab swaps therefore use `.tabswap` (below the transitions
     section) -- the outgoing screen simply leaves and only the incoming
     screen's CONTENT settles up. The header and the bottom bar never move,
     because they are persistent chrome, not part of the page. */
  .screen{position:absolute;inset:0;background:var(--bg);display:flex;flex-direction:column;transform:translateX(100%);
    transition:transform var(--t-screen) var(--ease),opacity var(--t-screen) var(--ease);visibility:hidden}
  .screen.active{transform:translateX(0);visibility:visible;z-index:2;opacity:1}
  .screen.behind{transform:translateX(-18%);visibility:visible;z-index:1;opacity:.55}
  .screen.pop{transform:translateY(100%)}
  .screen.pop.active{transform:translateY(0)}
  .scroll{flex:1;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:none}
  .scroll::-webkit-scrollbar{display:none}
  .hscroll{display:flex;overflow-x:auto;scrollbar-width:none;scroll-snap-type:x proximity}
  .hscroll::-webkit-scrollbar{display:none}
  /* PHASE 8.5D: .statusbar (a fake "9:41 / signal / wifi / battery" row,
     rendered once per screen) is REMOVED, markup and rule both -- real
     iPhone review found it rendering directly BELOW the real iOS status
     bar, so the device showed two status bars stacked on top of each
     other. It was never meant to survive contact with an actual phone: the
     comment it replaced called it "device chrome, not product content",
     written for the desktop preview frame (.phone gets a border/radius
     only above 479px; below that it already goes full-bleed). The browser
     owns the real status bar; the product does not get to draw a second
     one. See header.app/.subhead below for how the top spacing this used
     to provide is preserved without it. */

  /* ============ SHARED / GLASS ============ */
  .glass{background:var(--surface);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid var(--line);box-shadow:inset 0 1px 0 var(--glass-hi)}
  /* 40px: the smallest square that still reads as a comfortable target once
     the 24px glyph is centred in it. Was 38. */
  .icon-btn{background:none;border:none;color:var(--text-2);width:40px;height:40px;display:grid;place-items:center;border-radius:var(--radius-pill);transition:background .2s,color .2s;flex:0 0 auto}
  .icon-btn:hover{background:var(--surface-2);color:var(--text)}
  .icon-btn:active{transform:scale(.94)}
  /* PHASE 8.4: 38px min-height. Filter chips were 31px, which is a small
     target for a control the user hits repeatedly while browsing.
     PHASE 8.5E: real iPhone review found the label optically off-centre
     (worst on "All Breaks") despite align-items/justify-content already
     being correct at the flex-container level -- the browser's default
     line-height on a <button> leaves extra space below the glyph baseline
     that no font-metric-agnostic flex centering can remove, since it
     centres the LINE BOX, not the ink inside it. line-height:1 collapses
     that box to the font's own metrics, so the two centering mechanisms
     agree instead of fighting each other. */
  /* PHASE 8.5F FINAL POLISH ROUND 2 -- ONE SHARED CENTERING CONTRACT.
     "display:inline-flex;align-items:center;justify-content:center;
     line-height:1;white-space:nowrap" had been copy-pasted onto .chip,
     .seg button, .follow, .sgbtn, .vchip, .dhaz, .media .hazbadge and the
     verdict-pill family separately, one class at a time, across three
     separate passes -- which is exactly how .dr (the "NO RECENT REPORTS" /
     "FRESH CLIP" evidence-chip family) shipped WITHOUT justify-content at
     all until this pass caught it. Declared once here instead: every
     icon+text chip/pill in the app now inherits correct centering by
     construction, and a future pill added to this list is centred
     correctly automatically. Each class below still owns its own padding/
     colour/background/border/gap/font-size/flex-basis -- the properties
     that legitimately differ between them -- declared once each, never
     duplicated against this shared rule. */
  .chip,.seg button,.follow,.sgbtn,.vchip,.dhaz,.media .hazbadge,.dr,
  .spv-call .vpill,.dpill,.ss-v,.yvp{
    display:inline-flex;align-items:center;justify-content:center;
    line-height:1;white-space:nowrap}
  .chip{gap:6px;min-height:38px;
    border:1px solid var(--line);border-radius:var(--radius-pill);padding:0 15px;font-size:12px;font-weight:600;
    color:var(--text-2);background:var(--surface);transition:all .2s}
  .chip.on{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);font-weight:600}
  .chip:active{transform:scale(.96)}
  .cta{display:flex;align-items:center;justify-content:center;gap:8px;line-height:1;background:linear-gradient(115deg,var(--accent) 30%,var(--accent-2) 150%);color:var(--accent-ink);border:none;border-radius:var(--radius-pill);padding:14px 22px;font-size:14px;font-weight:700;transition:transform .15s}
  .cta:active{transform:scale(.97)}
  .cta.ghost{background:var(--surface);border:1px solid var(--line);color:var(--text);font-weight:600}
  /* PHASE 8.5F: partnerFollow() (Local Partner profile) toggles .on on this
     SAME .cta element with no dedicated "following" treatment of its own
     before this pass -- it stayed the full lime gradient CTA regardless of
     state, exactly the "giant fully-lime primary CTA for Following" the
     product review explicitly does not want. Same dark-restrained-pill +
     bright-text + lime-icon language as .follow.on, so the app has ONE
     Following visual language, not two different ones on two screens.

     PHASE 8.5F FINAL POLISH ROUND 2: same border fix as .follow.on below,
     for the same reason -- a generic var(--line) hairline gave it no edge
     of its own against its surrounding card. */
  .cta.on{background:var(--surface-2);color:var(--text);border:1px solid rgba(232,255,61,.22)}
  .cta.on i{color:var(--accent)}
  .live-dot{width:7px;height:7px;border-radius:50%;background:var(--accent);flex:0 0 auto}
  .live-dot.warn{background:var(--warn);animation:warnblink 1.4s ease-in-out infinite}
  @media (prefers-reduced-motion: reduce){ .live-dot.warn{animation:none} }
  @keyframes warnblink{0%,100%{opacity:1;box-shadow:0 0 0 0 rgba(217,160,91,.5)}50%{opacity:.55;box-shadow:0 0 8px 2px rgba(217,160,91,.4)}}
  .conds{display:grid;grid-template-columns:repeat(4,1fr);border-radius:var(--radius-card)}
  /* PHASE 8.4: four columns in a 348px card give ~87px each. At the old 14px
     side padding and 15px value size, "SE 8 kts" wrapped onto two lines and the
     WATER label clipped at the right edge. Tightened until all four columns hold
     their label and value on one line. */
  .cond{display:flex;flex-direction:column;gap:3px;padding:11px 9px;min-width:0}
  .cond + .cond{border-left:1px solid var(--line)}
  .cond label{font-size:10px;letter-spacing:.08em;font-weight:800;color:var(--text-2);display:flex;align-items:center;gap:4px;text-transform:uppercase;white-space:nowrap}
  /* PHASE 8.5E: Swell/Wind/Tide/Water share ONE icon colour, on purpose.
     Tide's arrow used to carry its own colour (.tup blue, .tdown orange-
     red) based on direction -- real iPhone review found this read as an
     accidental fifth status system next to it, not a deliberate one: four
     supporting forecast metrics, one of them singled out in a colour that
     means nothing elsewhere in the app. .tup/.tdown are GLOBAL classes
     (tideIcon() feeds every surface that shows the tide arrow -- this
     card, For You's decision detail, the upload composer preview), so
     removing their colour here removes the same accidental defect
     everywhere it appeared, not just in this one card. The classes
     themselves, and the up/down GLYPH they choose, are untouched --
     direction is still shown, just not colour-coded. Strong colour stays
     reserved for actual product meaning: GO/MAYBE/NOPE/HAZARD/REALITY. */
  .cond label i{color:var(--accent-2);font-size:13px}
  .cond strong{font-size:14px;font-weight:700;white-space:nowrap;letter-spacing:-.01em}
  /* PHASE 8.4: .subhead and header.app now share one geometry -- same height,
     same edge inset, same icon size -- so moving between a primary screen and a
     detail screen does not shift the top of the app. The 14px inset is smaller
     than the 20px content gutter on purpose: these rows hold 40px icon buttons,
     whose glyphs then land optically on the content gutter while keeping a
     full-size touch target at the screen edge.
     PHASE 8.5D: both gained env(safe-area-inset-top) added to their top
     padding, kept IDENTICAL on both rules on purpose (the "same geometry"
     invariant above still has to hold). This replaces the top clearance
     the removed fake .statusbar used to provide -- but correctly, via the
     real safe-area the browser reports, instead of a guessed fixed height
     that happened to roughly match some devices' notches and not others.
     viewport-fit=cover is already set in index.html's meta viewport, which
     is what makes env() resolve to a real value here instead of 0 on a
     phone; the 0px fallback keeps desktop/older-browser behaviour exactly
     as it was (env() support is required for the fallback syntax itself,
     not for a correct value -- unsupported browsers ignore the whole
     declaration and keep the plain 8px rule above it, still valid CSS). */
  .subhead{display:flex;align-items:center;gap:8px;padding:8px var(--gut-card) 12px;flex:0 0 auto;min-height:56px}
  .subhead{padding-top:calc(8px + env(safe-area-inset-top, 0px))}
  .subhead .ttl{flex:1;text-align:center;font-size:15px;font-weight:600;letter-spacing:-.01em}
  .subhead .spacer{width:40px}

  /* ============ FEED ============ */
  header.app{display:flex;align-items:center;gap:10px;padding:8px var(--gut-card) 12px;flex:0 0 auto;min-height:56px}
  header.app{padding-top:calc(8px + env(safe-area-inset-top, 0px))}
  /* PHASE 8.5B: .brand svg{color:var(--accent)} is gone -- the header brand
     no longer contains an <svg>, only the .spot-mark grid (see the SPOT
     WORDMARK block above). */
  .brand{display:flex;align-items:center;gap:7px;font-weight:700;letter-spacing:.06em;font-size:17px}
  .story{display:flex;flex-direction:column;align-items:center;gap:6px;flex:0 0 auto;background:none;border:none;scroll-snap-align:start}
  .story .ring{width:60px;height:60px;border-radius:50%;padding:2px;border:1.5px solid var(--line);transition:transform .16s cubic-bezier(.34,1.56,.64,1),border-color .2s}
  .story span{font-size:11px;color:var(--text-2);font-weight:500;max-width:64px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;transition:opacity .2s var(--ease)}
  .story:active .ring{transform:scale(.94)}
  .story.live .ring{position:relative;border:none;background:transparent}
  .story.live .ring::before{content:"";position:absolute;inset:0;border-radius:50%;background:conic-gradient(from 0deg,var(--accent),var(--accent-2) 35%,var(--accent) 65%,var(--accent-2) 90%,var(--accent));animation:ringspin 3.2s linear infinite}
  .story.live .ring img{position:relative;z-index:1}
  @keyframes ringspin{to{transform:rotate(360deg)}}
  /* PHASE 8.5 REAL-DEVICE CORRECTION: the spinning dashed ring (hzspin +
     breathe) read as loading/scanning, not attention. ::before is now a
     STATIC solid ring -- the hazard icon/text principle applied here too --
     and the new hz-attn pulse lives on ::after instead (defined with the rest
     of the shared hazard motion, near the verdict system, so this one ring
     is not a second implementation of the same idea). */
  .story.hz .ring{position:relative;border:none;background:transparent}
  .story.hz .ring::before{content:"";position:absolute;inset:0;border-radius:50%;background:#ff5b3c}
  .story.hz .ring img{position:relative;z-index:1}
  .story.none .ring{border-color:rgba(255,255,255,.13)}
  .story.none .ring img{filter:grayscale(.55) brightness(.72)}
  .story img{width:100%;height:100%;border-radius:50%;object-fit:cover}
  .story.live span,.story.hz span{color:var(--text)}
  .card{border-radius:var(--radius-card);overflow:hidden;margin:0 var(--gut-card) 14px}
  .card.compact .media{aspect-ratio:16/8;cursor:pointer}
  .card.compact .media .expandhint{position:absolute;bottom:10px;right:10px;width:28px;height:28px;border-radius:50%;background:rgba(8,16,24,.62);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.09);display:grid;place-items:center;color:var(--text);font-size:14px}
  .compact-who{display:flex;align-items:center;gap:9px;padding:11px 15px;cursor:pointer}
  .compact-who img{width:26px;height:26px;border-radius:50%;object-fit:cover}
  .compact-who .meta{flex:1;min-width:0}
  .compact-who .meta b{font-size:13px;font-weight:600;display:block}
  .compact-who .meta small{font-size:11px;color:var(--text-2)}
  .compact-who .expandtxt{font-size:11px;color:var(--text-2);opacity:.7}
  .card.compact.open .post{animation:cardopen .25s var(--ease) both}
  @keyframes cardopen{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
  .media{position:relative;aspect-ratio:3/2;cursor:pointer}
  .media img.poster{width:100%;height:100%;object-fit:cover}
  .media .spot{position:absolute;top:12px;left:12px;display:flex;align-items:center;gap:7px;background:rgba(8,16,24,.62);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.09);padding:6px 12px;border-radius:var(--radius-pill);font-size:11px;font-weight:600;letter-spacing:.08em;color:var(--text)}
  .media time{position:absolute;top:12px;right:12px;background:rgba(8,16,24,.62);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.09);padding:6px 11px;border-radius:var(--radius-pill);font-size:11px;font-weight:500;color:var(--text)}
  /* PHASE 8.5 REAL-DEVICE CORRECTION: the icon itself is now static -- see the
     "static Hazard icon/text remains permanently readable" principle -- and
     the badge gets the same expanding-ring pulse as every other genuine
     Hazard indicator (defined once, near the verdict system). */
  .media .hazbadge{position:absolute;top:12px;right:12px;flex:0 0 auto;gap:5px;background:rgba(8,12,20,.72);backdrop-filter:blur(8px);border:1px solid rgba(255,112,67,.55);padding:5px 10px;border-radius:var(--radius-pill);font-size:10px;font-weight:800;letter-spacing:.06em;color:#ff7043}
  .media .hazbadge i{font-size:13px}
  .media.hz time{top:50px}
  .play{position:absolute;inset:0;margin:auto;width:52px;height:52px;border-radius:50%;border:1px solid rgba(255,255,255,.25);background:rgba(8,16,24,.5);backdrop-filter:blur(6px);color:#fff;display:grid;place-items:center;transition:transform .18s}
  .play:active{transform:scale(.92)}
  .dur{position:absolute;left:12px;bottom:12px;display:flex;align-items:center;gap:5px;background:rgba(8,16,24,.62);backdrop-filter:blur(8px);padding:5px 10px;border-radius:var(--radius-pill);font-size:11px;font-weight:500;color:#fff}
  .card .conds{border:none;border-top:1px solid var(--line);border-radius:0;background:none}
  .post{padding:13px 15px;border-top:1px solid var(--line)}
  .post .who{display:flex;align-items:center;gap:10px}
  .post .who img{width:36px;height:36px;border-radius:50%;object-fit:cover;cursor:pointer}
  .post .who .meta{flex:1;min-width:0;cursor:pointer}
  .post .who .meta b{font-size:14px;font-weight:600;display:block}
  .post .who .meta small{font-size:12px;color:var(--text-2)}
  /* PHASE 8.5F FINAL VISUAL POLISH: real-device review found the icon+text
     content inside .follow/.sgbtn/.vchip/.dhaz/.media .hazbadge reading as
     shifted right after the earlier appearance-reset + line-height:1 pass.
     Audited against the already-correct shared pattern (.chip, .seg button,
     the .vpill/.dpill/.ss-v/.yvp verdict-pill family): every one of THOSE
     declares BOTH `display:inline-flex` (shrink-wraps to content -- a bare
     `display:flex` is a BLOCK-level box that can render wider than its
     content in some parent contexts) AND `justify-content:center` (centres
     the icon+text pair WITHIN that box). These five classes had neither --
     .follow/.sgbtn/.media .hazbadge used bare `display:flex` with NO
     justify-content at all (defaulting to flex-start, i.e. content packed
     toward one edge rather than centred, if the box is ever wider than its
     content), and .vchip/.dhaz, while already `inline-flex`, were also
     missing `justify-content:center`. Brought all five in line with the
     one already-working pattern instead of adding a per-class padding/
     margin/transform nudge. */
  .follow{font-size:12px;font-weight:600;color:var(--accent);background:none;border:1px solid rgba(232,255,61,.4);min-height:38px;padding:0 15px;border-radius:var(--radius-pill);flex:0 0 auto;gap:5px;transition:all .2s}
  /* PHASE 8.5F: FOLLOWING is an active relationship, not a spent/disabled
     control -- real device review found it reading as disabled because it
     reused the generic muted-grey (--text-2, --line) treatment. Restyled
     as a dark, restrained pill (background:var(--surface-2), not the lime
     outline NOT-FOLLOWING uses and deliberately not a giant lime fill
     either) with BRIGHT primary text (--text, not muted) and a lime check
     icon (.follow.on i, below) as the one active accent -- "confirmed
     relationship", not "nothing to do here".

     PHASE 8.5F FINAL POLISH ROUND 2: STILL read as too grey/disabled on
     real-device review. The text/background/icon above were already
     correct; the remaining problem was the border -- var(--line) is a
     generic 12%-opacity hairline shared by every neutral divider/outline
     in the app, so the pill had no edge of its own and read as flat rather
     than as an active, intentional control. Given a faint lime tint tying
     it to the same active accent the check-icon already uses -- low enough
     opacity to stay clearly quieter than NOT-following's own lime outline
     (.4 opacity) below, high enough to actually be visible as an edge. */
  .follow.on{color:var(--text);background:var(--surface-2);border-color:rgba(232,255,61,.22)}
  .follow.on i{color:var(--accent)}
  .follow:active{transform:scale(.95)}
  .post p{font-size:13px;line-height:1.5;margin:10px 0 12px}
  .reactions{display:flex;gap:8px;align-items:center}
  .react{display:flex;align-items:center;justify-content:center;gap:6px;min-height:40px;font-size:13px;font-weight:600;color:var(--text-2);background:none;border:1px solid var(--line);padding:0 14px;border-radius:var(--radius-pill);transition:all .2s}
  .react.on{color:var(--accent);border-color:rgba(232,255,61,.45)}
  .react.on.angry{color:var(--warn);border-color:rgba(217,160,91,.45)}
  .react:active{transform:scale(.94)}
  .react .em{font-size:14px}
  .comments-link{margin-left:auto;min-height:40px;background:none;border:none;color:var(--text-2);font-size:12px;font-weight:600;display:flex;gap:6px;align-items:center}
  .comments-link:active{color:var(--text)}
  .noreport{margin:0 var(--gut-card) 14px;padding:24px 20px;text-align:center;border-radius:var(--radius-card)}
  .noreport i{font-size:24px;color:var(--off)}
  .noreport b{display:block;font-size:14px;font-weight:600;margin:10px 0 4px}
  .noreport span{font-size:12px;color:var(--text-2)}

  /* ============ TAB BAR ============ */
  /* PHASE 8.4: the bar tinted rgba(10,18,12,.9) -- a green-black that matched
     nothing else in the app. It now sits on the same navy surface as every
     other chrome element, and each destination is a 44px target with a fixed
     label baseline so nothing shifts between tabs. */
  .tabbar{display:flex;align-items:center;justify-content:space-around;border-top:1px solid var(--line);
    padding:8px 6px calc(10px + env(safe-area-inset-bottom));flex:0 0 auto;
    background:rgba(11,17,24,.92);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
  .tab{background:none;border:none;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;
    color:var(--text-2);font-size:10px;font-weight:600;letter-spacing:.01em;transition:color .2s;min-width:56px;min-height:44px}
  .tab i{font-size:22px;line-height:1}
  .tab.active{color:var(--accent)}
  .tab.active i{font-weight:inherit}
  .tab:active{transform:scale(.94)}
  /* The one creation action: larger, filled, and the only accent-on-accent
     element in the bar. */
  .fab{width:52px;height:52px;border-radius:50%;border:none;background:var(--accent);color:var(--accent-ink);
    display:grid;place-items:center;font-size:24px;box-shadow:0 4px 16px rgba(232,255,61,.22);transition:transform .18s}
  .fab:active{transform:scale(.9)}

  /* ============ SPOT VIEW ============
     PHASE 8.3: .spot-hero, .score, .verdict, .vcall and .crowdbig were the
     eleven-peer-card layout's styles. They are replaced by the .spv-* set at
     the end of this file, which follows the decision hierarchy: DECISION ->
     REALITY -> WHY -> FORECAST SUPPORT -> PRACTICAL -> deeper. Removed here
     rather than left dead, since every one of them had exactly one caller. */
  .section{padding:0 var(--gut);margin-bottom:18px}
  .section h4{font-size:11px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2);margin-bottom:10px;display:flex;justify-content:space-between;align-items:center}
  .section h4 small{font-size:11px;letter-spacing:0;font-weight:500;text-transform:none}
  /* PHASE 8.4: one section-label style. Five render functions carried an inline
     copy of these exact declarations in a style="" attribute; they now use this
     class instead, so the label can never drift per screen again. */
  .eyebrow{display:block;font-size:var(--fs-eyebrow);letter-spacing:var(--track-eyebrow);font-weight:800;
    color:var(--text-2);padding:0 var(--gut);margin-bottom:10px}
  .eyebrow small{font-size:11px;letter-spacing:0;font-weight:500;text-transform:none;opacity:.85;margin-left:6px}
  .eyebrow.flat{padding:0}
  .fc-label{display:inline-flex;align-items:center;gap:6px;font-size:10px;letter-spacing:.1em;font-weight:800;color:var(--text-2);border:1px solid var(--line);border-radius:var(--radius-pill);padding:4px 10px;margin-bottom:10px}
  .fc-label i{color:var(--accent)}
  .clips{gap:10px;padding:0 var(--gut) 4px}
  .clip{flex:0 0 128px;scroll-snap-align:start;background:none;border:none;text-align:left}
  .clip .thumb{position:relative;border-radius:var(--r-md);overflow:hidden;border:1px solid var(--line)}
  .clip img{width:128px;height:80px;object-fit:cover}
  .clip .cdur{position:absolute;left:7px;bottom:7px;background:rgba(8,16,24,.66);padding:3px 7px;border-radius:var(--radius-pill);font-size:10px;color:#fff;display:flex;gap:4px;align-items:center}
  .clip .cplay{position:absolute;inset:0;margin:auto;width:26px;height:26px;border-radius:50%;background:rgba(8,16,24,.5);border:1px solid rgba(255,255,255,.2);display:grid;place-items:center;color:#fff;font-size:10px}
  .clip .cmeta{font-size:11px;color:var(--text-2);margin-top:6px;display:block}
  /* PHASE 8.5 REAL-DEVICE CORRECTION: the banner box itself no longer
     animates (hazglow's box-shadow breathing is gone), and the icon glyph
     is static by default (hazbre removed -- it inherits #ff7043 from .sign
     by ordinary CSS inheritance, so no rule is needed for its colour).
     PHASE 8.6 HAZARD REFINEMENT: the plain static left accent bar
     (.hazard::before, a 5px strip) is REMOVED -- real feedback called the
     card noisy, and the strip was a second emphasis device competing with
     the icon, the uppercase label colour and the border/background tint
     already doing that job. The one motion this banner now keeps is a
     soft pulse on the icon glyph ITSELF (.sign i, hzPulse, defined once
     near the verdict system alongside every other genuine Hazard surface)
     -- not a ring around .sign anymore; see that block for why. */
  .hazard{position:relative;display:flex;gap:13px;align-items:center;margin:0 var(--gut) 18px;padding:13px 16px 13px 20px;border-radius:var(--r-md);overflow:hidden;background:rgba(255,80,40,.06);border:1px solid rgba(255,112,67,.24)}
  .hazard .sign{position:relative;width:42px;height:42px;flex:0 0 auto;display:grid;place-items:center;color:#ff7043;font-size:24px}
  .hazard .txt{min-width:0}
  .hazard .txt b{display:block;font-size:11px;letter-spacing:.18em;font-weight:800;color:#ff7043}
  .hazard .txt span{font-size:13px;color:var(--text);opacity:.62;line-height:1.4;display:block;margin-top:2px}
  .intel-tabs{display:flex;gap:8px;margin-bottom:10px}
  .intel{border-radius:var(--r-md);overflow:hidden;border:1px solid var(--line);position:relative}
  .intel img{width:100%;aspect-ratio:16/9;object-fit:cover}
  .intel .cap{position:absolute;left:12px;bottom:12px;background:rgba(8,16,24,.66);backdrop-filter:blur(6px);padding:5px 11px;border-radius:var(--radius-pill);font-size:11px;font-weight:500}
  .pad{padding:0 var(--gut) 22px}

  /* ============ UPLOAD ============ */
  .cam{flex:1;position:relative;overflow:hidden}
  .cam img.bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
  .cam .top{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:14px 14px;background:linear-gradient(rgba(6,12,19,.55),transparent)}
  .cam .top .t{font-size:13px;font-weight:600;letter-spacing:var(--track-eyebrow);color:#fff}
  .cam .frame{position:absolute;inset:18px;border:1px solid rgba(255,255,255,.35);border-radius:20px;pointer-events:none}
  .cam .camspot{position:absolute;top:64px;left:0;right:0;display:flex;justify-content:center}
  .cam .camspot span{display:flex;align-items:center;gap:6px;background:rgba(8,16,24,.6);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.1);padding:6px 12px;border-radius:var(--radius-pill);font-size:11px;font-weight:500;color:#fff}
  .cam .camspot i{color:var(--accent)}
  .cam .recbar{position:absolute;bottom:0;left:0;right:0;padding:20px;display:flex;flex-direction:column;align-items:center;gap:12px;background:linear-gradient(transparent,rgba(6,12,19,.6))}
  .cam .max{font-size:11px;font-weight:600;letter-spacing:var(--track-eyebrow);color:rgba(255,255,255,.85)}
  .rec{width:68px;height:68px;border-radius:50%;border:3px solid transparent;background:linear-gradient(var(--bg),var(--bg)) padding-box,conic-gradient(from 90deg,var(--accent),var(--accent-2),var(--accent)) border-box;display:grid;place-items:center;transition:transform .15s}
  .rec:active{transform:scale(.92)}
  .rec .dot{width:46px;height:46px;border-radius:50%;background:#e0503f;transition:all .25s var(--ease)}
  .cam.recording .rec .dot{width:26px;height:26px;border-radius:8px}
  .cam .timer{font-size:13px;font-weight:600;color:#fff;font-variant-numeric:tabular-nums;min-height:18px}
  .steps{display:flex;gap:6px;justify-content:center;padding:12px}
  .steps i{width:22px;height:3px;border-radius:2px;background:var(--surface-2);transition:background .3s}
  .steps i.on{background:var(--accent)}
  .posted{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:30px;text-align:center}
  .posted .ok{width:84px;height:84px;border-radius:50%;border:1.5px solid var(--accent);display:grid;place-items:center;color:var(--accent);font-size:36px;animation:pop .5s var(--ease) both}
  @keyframes pop{from{transform:scale(.6);opacity:0}to{transform:scale(1);opacity:1}}
  .posted h2{font-size:24px;font-weight:700}
  .posted p{font-size:13px;color:var(--text-2);line-height:1.6}
  .posted .cta{margin-top:8px;width:100%}

  /* ============ PROFILE ============ */
  .profile-top{display:flex;align-items:center;gap:16px;padding:10px var(--gut) 14px}
  .profile-top img.av{width:74px;height:74px;border-radius:50%;object-fit:cover;border:1.5px solid var(--accent);padding:2px}
  .profile-top .stats{flex:1;display:flex;justify-content:space-around;text-align:center}
  .profile-top .stats b{font-size:17px;font-weight:700;display:block}
  .profile-top .stats span{font-size:11px;color:var(--text-2)}
  .bio{padding:0 var(--gut) 14px}
  .bio h2{font-size:20px;font-weight:700}
  .bio p{font-size:13px;color:var(--text-2);line-height:1.55;margin-top:4px}
  .tags{display:flex;gap:8px;padding:0 var(--gut) 16px;flex-wrap:wrap}
  .tag{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:500;color:var(--text-2);background:var(--surface-solid);border:1px solid var(--line);border-radius:var(--radius-pill);padding:7px 13px}
  .tag i{color:var(--accent);font-size:13px}
  .quiver{gap:12px;padding:0 var(--gut) 4px}
  .board{flex:0 0 108px;scroll-snap-align:start}
  .board .bimg{width:108px;height:150px;border-radius:var(--r-md);overflow:hidden;border:1px solid var(--line);background:var(--surface-2);box-shadow:0 6px 18px rgba(0,0,0,.35)}
  .board img{width:100%;height:100%;object-fit:cover}
  .board b{font-size:12px;font-weight:600;display:block;margin-top:7px}
  .board span{font-size:11px;color:var(--text-2)}
  .fav{gap:12px;padding:0 var(--gut) 4px}
  .clipgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:3px;padding:0 var(--gut) 20px}
  .clipgrid img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:var(--r-sm)}
  .insta-link{display:inline-flex;align-items:center;gap:6px;min-height:40px;color:var(--accent);font-size:13px;font-weight:600;background:none;border:none;padding:0}

  /* ============ DISCOVER / MAP ============ */
  .seg{display:flex;background:var(--surface-solid);border:1px solid var(--line);border-radius:var(--radius-pill);padding:3px;margin:4px var(--gut) 12px}
  /* PHASE 8.5E: same optical-centering fix as .chip, same root cause (a
     default line-height leaving asymmetric space around the glyph inside
     the padding box). .seg button was relying on padding alone for
     vertical position with no flex centering of its own text at all --
     the padding itself stays exactly as it was (the control's overall
     height/geometry is unchanged), only the text within it is now
     explicitly centred. */
  .seg button{flex:1;
    border:none;background:none;color:var(--text-2);font-size:13px;font-weight:600;padding:8px;border-radius:var(--radius-pill);transition:all .2s}
  /* Phase 8 correction: compact variant used ONLY by the Spots subhead, so the
     Map/List switch no longer costs a dedicated vertical block. Scoped by the
     .compact class so no other .seg on any screen is affected. */
  .seg.compact{margin:0;padding:2px;flex:0 0 auto;width:132px}
  .seg.compact button{font-size:11px;padding:10px 0;font-weight:800;letter-spacing:.02em}
  .seg button.on{background:var(--accent);color:var(--accent-ink)}
  .mapwrap{position:relative;flex:1;overflow:hidden;margin:0 var(--gut-card);border-radius:var(--radius-card);border:1px solid var(--line);min-height:200px}
  #leafmap{position:absolute;inset:0;background:#0a141f}
  .leaflet-container{font-family:'Outfit',sans-serif}
  .leaflet-tile{filter:saturate(.55) brightness(.95)}
  .leaflet-control-attribution{background:rgba(10,20,31,.6) !important;color:#445 !important;font-size:8px !important}
  .leaflet-control-attribution a{color:#667 !important}
  /* Phase 8 correction: unselected spots are a small state dot, not a named
     pill. A dozen named pills inside the Bukit peninsula overlapped into an
     unreadable pile at the default zoom. */
  .spot-dot{width:13px;height:13px;border-radius:50%;background:var(--off);border:2px solid rgba(8,16,24,.9);
    box-shadow:0 0 0 1px rgba(255,255,255,.14),0 1px 4px rgba(0,0,0,.5);transform:translate(-50%,-50%);cursor:pointer}
  .spot-dot.sd-go{background:var(--accent);box-shadow:0 0 0 1px rgba(232,255,61,.5),0 0 9px rgba(232,255,61,.45)}
  .spot-dot.sd-maybe{background:var(--warn)}
  .spot-dot.sd-nope,.spot-dot.sd-active{background:var(--text-2)}
  .spot-dot.sd-hazard{background:#ff5b3c;box-shadow:0 0 0 1px rgba(255,91,60,.55),0 0 9px rgba(255,91,60,.5)}
  .spot-dot.sd-off{background:var(--off);opacity:.75}
  .spot-pin.sel{border-color:rgba(232,255,61,.5);box-shadow:0 3px 14px rgba(0,0,0,.55);z-index:900}
  .spot-pin.sel.sd-go .pdot{background:var(--accent)}
  .spot-pin.sel.sd-maybe .pdot{background:var(--warn)}
  .spot-pin.sel.sd-hazard .pdot{background:#ff5b3c}
  /* PHASE 8.5D: .spot-pin.sel's own border-color above is lime -- the GO/
     accent colour -- applied to EVERY selected pin regardless of verdict.
     Real iPhone review found a selected MAYBE+HAZARD spot (Uluwatu) reading
     as though it were GO, because selection and verdict were sharing one
     colour. This 3-class selector is more specific than the 2-class rule
     above, so it wins on specificity alone, source order aside. Selection
     now matches the ACTUAL hazard colour instead of borrowing GO's -- never
     imply GO+HAZARD when the real call is MAYBE (or NOPE)+HAZARD. */
  .spot-pin.sel.sd-hazard{border-color:rgba(255,91,60,.55)}
  .spot-pin{position:relative;display:flex;align-items:center;gap:6px;background:rgba(8,16,24,.78);backdrop-filter:blur(6px);border:1px solid var(--line);border-radius:var(--radius-pill);padding:5px 10px 5px 8px;font-size:11px;font-weight:600;color:var(--text);white-space:nowrap;transform:translate(-8px,-50%)}
  .spot-pin .pdot{width:8px;height:8px;border-radius:50%;background:var(--off);flex:0 0 auto}
  /* PHASE 8.3: .spot-pin.me (the "You" pin) and .nearme (the "Near Me" button)
     were removed with the mock-location features they styled. */
  /* PHASE 8.4 FINAL CORRECTION: 2x2 instead of a four-row column.
     Four stacked rows made an 83px-tall panel sitting over the top-left of the
     map -- a lot of overlay for a key nobody reads twice. Same four meanings,
     same colours, same words, no interaction added: the footprint just stops
     competing with the thing it annotates. Height roughly halves, and the extra
     width stays clear of the Bukit marker cluster. */
  .maplegend{position:absolute;top:10px;left:10px;z-index:500;display:grid;
    grid-template-columns:repeat(2,auto);gap:3px 12px;background:rgba(8,16,24,.72);
    backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid var(--line);
    border-radius:var(--r-sm);padding:6px 9px;font-size:10px;line-height:1.2;color:var(--text-2)}
  .maplegend div{display:flex;align-items:center;gap:5px;white-space:nowrap}
  .maplegend .pdot{width:6px;height:6px;border-radius:50%;flex:0 0 auto}
  /* PHASE 8.4: the collapsed peek must still fit its one summary row inside
     20% of the map, and the shared reality chip is taller than the bare text it
     replaced. The height came out of PADDING, never out of the map: the sheet
     stays at 20% and the map stays dominant. */
  .sheet{flex:0 0 auto;border-top:1px solid var(--line);border-radius:22px 22px 0 0;padding:4px 0 6px;position:relative;z-index:600}
  /* Phase 8 correction: was 40%, which still read as "map + list". The sheet now
     PEEKS by default and only becomes a list when the user asks for one. */
  /* PHASE 8.5: the sizing and the collapsed state moved to the motion block at
     the end of this file. The sheet is now a fixed-height box that is
     TRANSLATED down to reveal the peek, so a drag costs one transform instead
     of relaying out the list on every frame. The settled geometry is unchanged:
     peek still covers 20% of the map. */
  /* A 2px-tall drag affordance was not a usable target. The handle looks the
     same; the button around it is now 32px. */
  .sheet .grabrow{display:block;width:100%;background:none;border:0;padding:7px 0 5px;cursor:pointer;min-height:24px}
  .sheet-head{display:flex;align-items:baseline;gap:8px;padding:0 var(--gut) 3px;flex:0 0 auto}
  .sheet-head b{font-size:12px;font-weight:700;letter-spacing:-.01em}
  .sheet-head span{font-size:10px;letter-spacing:.1em;font-weight:800;color:var(--text-2)}
  /* The one compact summary row shown while collapsed. */
  .sheet-sel{flex:0 0 auto;padding:0 var(--gut-card) 7px}
  .ss-row{display:flex;align-items:center;gap:10px;width:100%;padding:7px 10px;border-radius:var(--r-chip);min-height:48px;
    border:1px solid var(--line);background:rgba(255,255,255,.04);color:var(--text);font-family:inherit;
    text-align:left;cursor:pointer}
  .ss-row:active{background:var(--surface-2)}
  .ss-main{flex:1 1 auto;min-width:0}
  .ss-name{display:block;font-size:13px;font-weight:700;letter-spacing:-.01em;line-height:1.25}
  .ss-meta{display:flex;align-items:center;gap:6px;margin-top:2px;flex-wrap:nowrap;overflow:hidden}
  /* .ss-v is styled by the shared verdict system near the end of this file. */
  /* nowrap: without it the window text broke onto a second line inside a row
     that has exactly one line of vertical room, pushing the summary out of the
     collapsed peek. The reality chip clips instead, which is the right thing to
     lose first in a 330px row. */
  .ss-win{font-size:10px;font-weight:700;color:var(--accent-2);white-space:nowrap;flex:0 0 auto}
  /* One compact step for the pills inside the map peek -- same system, smaller
     step, because this row lives in 20% of a map rather than on a page. */
  .ss-meta .ss-v,.ss-meta .dr{padding:3px 8px}
  /* PHASE 8.4: .ss-live / .ss-dot / .ss-some / .ss-none removed. The sheet now
     renders the shared realityChip(), so this screen no longer keeps a private
     copy of the reality language. */
  .ss-row>i{flex:0 0 auto;font-size:14px;color:var(--text-2)}
  /* Filters and the full list exist only in the expanded sheet. */
  .sheet-body{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}
  /* PHASE 8.5: the body is no longer display:none when collapsed. It has to
     exist for a drag to reveal anything -- otherwise the user pulls open an
     empty sheet and the content appears only at the end of the gesture. While
     collapsed it simply sits below the map's clip. */
  .sheet .grab{width:38px;height:4px;border-radius:2px;background:var(--surface-2);margin:0 auto;flex:0 0 auto}
  .filters{gap:8px;padding:0 var(--gut) 10px;flex:0 0 auto}
  /* PHASE 8.5 REAL-DEVICE CORRECTION: the honest empty state (Map, no
     selection) and the compact quick-context bridge card (Map, selected) that
     replaced the directory rows -- see renderSheetSel()/renderSheetExpand()
     in discover-map.js. Deliberately small: no forecast explanation, no
     other spot cards, no Local Knowledge, no partner content. */
  .ss-empty{display:flex;flex-direction:column;align-items:center;gap:6px;padding:20px 10px 10px;
    color:var(--text-2);font-size:12px;text-align:center}
  .ss-empty i{font-size:20px;opacity:.6}
  .ss-empty.lg{padding:26px 10px 14px}
  .ss-empty.lg i{font-size:24px}
  .ss-expand{padding:2px var(--gut-card) 4px}
  .ss-ehead{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
  .ss-ehead b{font-size:15px;font-weight:700;letter-spacing:-.01em}
  .ss-ecall{display:flex;align-items:center;gap:6px;flex:0 0 auto}
  .ss-esec{margin-bottom:10px}
  .ss-esec h6{font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2);margin:0 0 4px}
  .ss-esec span{font-size:13px;color:var(--text)}
  .ss-ereal{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
  .ss-rnote{font-size:11px;color:var(--text-2)}
  /* PHASE 8.5 REAL-DEVICE CORRECTION: matches .dd-open (For You's "Open [spot]"
     button) rather than an accent-filled CTA -- "open the detail screen" is the
     same action everywhere in the product and should not visually compete with
     an actual GO verdict, which is the only thing that owns the solid accent
     fill (see the shared verdict system). Caught on-device next to Uluwatu's
     hazard-and-accent-yellow button sitting side by side. */
  .ss-eopen{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;
    margin-top:2px;padding:11px;border-radius:var(--r-chip);min-height:44px;
    border:1px solid var(--line);background:rgba(255,255,255,.04);color:var(--text);
    font-family:inherit;font-size:13px;font-weight:700;cursor:pointer}
  .spotrow{display:flex;align-items:center;gap:12px;width:100%;background:none;border:none;border-top:1px solid var(--line);padding:12px var(--gut);text-align:left;color:var(--text);transition:background .15s;min-height:64px}
  .spotrow:active{background:var(--surface-2)}
  .spotrow img{width:46px;height:46px;border-radius:var(--r-chip);object-fit:cover}
  .spotrow .info{flex:1;min-width:0}
  /* PHASE 8.4: verdict first, then the spot, then its authored properties. The
     rating is no longer the accent-coloured hero of the row -- GO owns that
     colour, everywhere. */
  .spotrow .info .sr-top{display:flex;align-items:center;gap:9px;min-width:0}
  .spotrow .info b{font-size:14px;font-weight:700;letter-spacing:-.01em;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .spotrow .info .sr-sub{display:block;font-size:12px;color:var(--text-2);margin-top:3px}
  .spotrow .right{text-align:right;flex:0 0 auto}
  .connlink{display:flex;align-items:center;gap:7px;width:100%;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-chip);padding:12px 14px;margin-top:12px;color:var(--text);font-size:13px;text-align:left;min-height:48px}
  .connlink b{font-weight:700}
  .connlink .connlink-dot{color:var(--text-2)}
  .connlink i{margin-left:auto;color:var(--text-2)}
  .connrow{display:flex;align-items:center;gap:12px;width:100%;background:none;border:none;border-top:1px solid var(--line);padding:12px var(--gut);text-align:left;color:var(--text);transition:background .15s;min-height:60px}
  .connrow:active{background:var(--surface-2)}
  .connrow img{width:44px;height:44px;border-radius:50%;object-fit:cover}
  .connrow .meta{flex:1;min-width:0}
  .connrow .meta b{font-size:14px;font-weight:600;display:block}
  .connrow .meta span{font-size:12px;color:var(--text-2)}
  .spotrow .right b{font-size:13px;font-weight:700;color:var(--text)}
  .spotrow .right b.dash{color:var(--text-2)}
  .spotrow .right .fireline{font-size:11px;color:var(--text-2);display:block;margin-top:2px}
  .listview{flex:1;overflow-y:auto;display:none}

  /* PHASE 8.4: .feedTabs finally has a rule.
     ARCHITECTURE.md documented this defect and deferred it to "a future phase,
     to be scoped and approved on its own rather than folded into unrelated
     work": index.html has `<div class="feedTabs" id="connTabs">`, but the only
     matching-looking selector was `nav.tabs`, which requires a literal <nav
     class="tabs">. It matched nothing, so the Connections screen's Following /
     Followers / Buddies strip rendered as three default browser buttons --
     present verbatim since the first commit.
     This IS that phase: it is a segmented control with a 21px touch target on a
     pass whose brief names segmented controls and touch targets explicitly, and
     `nav.tabs` was removed here as dead CSS, so leaving it now would mean no
     rule existed anywhere. It borrows the submode language used on Reports
     rather than inventing a fourth tab style. Markup and click wiring are
     untouched -- the .active class the handler already sets is what this
     styles. */
  .feedTabs{display:flex;gap:8px;padding:8px var(--gut) 4px;flex:0 0 auto}
  /* No transition here on purpose: Phase 8.4 adds no motion, and this rule is
     new. It borrows .chip's geometry, not .chip's easing. */
  .feedTabs button{display:inline-flex;align-items:center;justify-content:center;min-height:38px;
    border:1px solid var(--line);border-radius:var(--radius-pill);padding:0 15px;font-size:12px;font-weight:600;
    color:var(--text-2);background:var(--surface);font-family:inherit;white-space:nowrap}
  .feedTabs button.active{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);font-weight:700}
  .feedTabs button:active{transform:scale(.96)}

  /* ============ SEARCH ============ */
  .searchbox{display:flex;align-items:center;gap:10px;margin:4px var(--gut) 14px;background:var(--surface-solid);border:1px solid var(--line);border-radius:var(--radius-pill);padding:11px 16px}
  .searchbox i{color:var(--text-2);font-size:17px}
  .searchbox input{flex:1;background:none;border:none;outline:none;color:var(--text);font-family:inherit;font-size:14px}
  .searchbox input::placeholder{color:var(--text-2)}
  .searchres .lbl{font-size:var(--fs-eyebrow);letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2);padding:8px var(--gut)}
  .userrow{display:flex;align-items:center;gap:12px;width:100%;background:none;border:none;border-top:1px solid var(--line);padding:12px var(--gut);text-align:left;color:var(--text);min-height:60px}
  .userrow img{width:40px;height:40px;border-radius:50%;object-fit:cover}
  .userrow .info{flex:1}
  .userrow .info b{font-size:14px;font-weight:600;display:block}
  .userrow .info span{font-size:12px;color:var(--text-2)}

  /* ============ SHEETS (comments / picker) ============ */
  .overlay{position:absolute;inset:0;z-index:60;background:rgba(4,8,13,.55);opacity:0;visibility:hidden;transition:opacity .3s;display:flex;flex-direction:column;justify-content:flex-end}
  .overlay.open{opacity:1;visibility:visible}
  .drawer{border-radius:22px 22px 0 0;border-top:1px solid var(--line);padding:12px 0 calc(16px + env(safe-area-inset-bottom));transform:translateY(30%);transition:transform .35s var(--ease);max-height:72%;display:flex;flex-direction:column;background:rgba(13,24,36,.96);backdrop-filter:blur(18px)}
  .qrbox{width:180px;height:180px;margin:14px auto 12px;border-radius:20px;background:var(--surface-2);border:1.5px solid var(--line);display:grid;place-items:center;color:var(--accent);font-size:64px}
  .qrhint{font-size:12px;color:var(--text-2);padding:0 30px;line-height:1.5}
  .buddynote{display:flex;align-items:center;gap:7px;margin-top:10px;font-size:12px;color:var(--accent-2)}
  .overlay.open .drawer{transform:translateY(0)}
  .drawer .grab{width:36px;height:4px;border-radius:2px;background:var(--surface-2);margin:0 auto 8px}
  .drawer h4{font-size:14px;font-weight:700;padding:4px 20px 10px}
  .comment{padding:10px 20px;border-top:1px solid var(--line)}
  .comment .who{display:flex;gap:8px;align-items:center;margin-bottom:3px}
  .comment .who b{font-size:13px;font-weight:600}
  .comment .who span{font-size:11px;color:var(--text-2)}
  .comment p{font-size:13px;line-height:1.5;color:var(--text)}
  .cinput{display:flex;gap:8px;padding:12px 20px 4px;border-top:1px solid var(--line);margin-top:auto}
  .cinput .field{flex:1;display:flex;align-items:center;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--radius-pill);padding:0 6px 0 14px}
  .cinput input{flex:1;background:none;border:none;outline:none;color:var(--text);font-family:inherit;font-size:13px;min-width:0}
  .cinput .count{font-size:10px;color:var(--text-2);padding:0 8px;font-variant-numeric:tabular-nums}
  .sendbtn{width:38px;height:38px;border-radius:50%;border:none;background:var(--accent);color:var(--accent-ink);display:grid;place-items:center;font-size:16px}
  .sendbtn:active{transform:scale(.9)}
  .efield{padding:8px 20px}
  .efield label{display:block;font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2);margin-bottom:5px}
  .efield input,.efield textarea,.efield select{width:100%;background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-chip);padding:11px 13px;color:var(--text);font-size:14px;font-family:inherit;outline:none;transition:border-color .2s}
  .efield input:focus,.efield textarea:focus,.efield select:focus{border-color:var(--accent)}
  .efield textarea{resize:none;height:64px;line-height:1.45}
  .efield select{appearance:none;-webkit-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%237d9070'%3E%3Cpath d='M6 8.5L1.5 4h9z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 13px center}
  .epick{display:flex;gap:10px;padding:4px 20px 8px}
  .epick img{width:52px;height:52px;border-radius:50%;object-fit:cover;border:2px solid transparent;cursor:pointer;transition:all .25s var(--ease);opacity:.55}
  .epick img.on{border-color:var(--accent);opacity:1;transform:scale(1.08);box-shadow:0 0 14px rgba(232,255,61,.3)}
  .eboard{display:flex;gap:8px;align-items:center}
  .eboard input{flex:1}
  .eboard input.sz{flex:0 0 74px;text-align:center}
  .picklist{overflow-y:auto;scrollbar-width:none}
  .picklist::-webkit-scrollbar{display:none}
  /* PHASE 8.3: .km removed -- the spot picker no longer prints a mock distance. */

  /* ============ TIER FEATURES ============ */
  .stillgood{display:flex;align-items:center;gap:8px;padding:9px 15px;border-top:1px solid var(--line);font-size:12px;color:var(--text-2)}
  .sgbtn{margin-left:auto;font-size:11px;font-weight:700;color:var(--accent);background:none;border:1px solid rgba(232,255,61,.4);min-height:36px;padding:0 13px;border-radius:var(--radius-pill);gap:5px;flex:0 0 auto;transition:all .2s}
  .sgbtn:active{transform:scale(.95)}
  /* PHASE 8.5F: "Confirmed by N surfer(s)" is EVIDENCE, not a spent button.
     .sgbtn's un-suffixed state is a lime CTA ("Still accurate?" -- ask for
     confirmation); .done used to reuse the generic "disabled control" grey
     (--text-2, --line) once there was nothing left to tap -- correct for a
     used-up BUTTON, wrong for what it actually communicates once it stops
     being clickable. Real device review: it read as disabled, not as
     confirmed. Restyled with the SAME mint/teal evidence colour the
     Reality chip already uses for confirmed evidence (.dr.live below) --
     one coherent Reality Evidence language, GO's lime deliberately not
     reused here (a decision colour, not an evidence one). pointer-events
     stays none: it still has nothing left to do, that part was correct.

     PHASE 8.5F FINAL POLISH ROUND 2: STILL read as too weak on real-device
     review. Background/border opacity nudged up (.12->.18, .3->.42) for
     more presence as an evidence pill; the mint colour, icon and text are
     unchanged -- this is a strength adjustment of the SAME treatment, not a
     different one. */
  .sgbtn.done{color:var(--accent-2);background:rgba(77,255,200,.18);border-color:rgba(77,255,200,.42);pointer-events:none}
  .trustchip{font-size:10px;font-weight:700;color:var(--accent);background:rgba(232,255,61,.1);border-radius:var(--radius-pill);padding:2px 7px;vertical-align:1px}
  .buddies{display:flex;align-items:center;gap:10px;margin:0 var(--gut) 18px;padding:12px 15px;border-radius:var(--r-chip);font-size:12px;color:var(--text)}
  .buddies .avstack{display:flex;flex:0 0 auto}
  .buddies .avstack img{width:24px;height:24px;border-radius:50%;border:2px solid var(--bg);object-fit:cover;margin-left:-8px}
  .buddies .avstack img:first-child{margin-left:0}
  .buddies span{color:var(--text-2)}
  .drift{margin-top:12px;padding-top:12px;border-top:1px solid var(--line);font-size:12px;color:var(--text-2);line-height:1.55}
  .drift b{color:var(--accent);font-weight:600;font-size:10px;letter-spacing:var(--track-eyebrow)}
  /* PHASE 8.4: the notification panel used the standard .glass surface (82%
     alpha) and floated over the decision list, so GO cards and spot names read
     straight through the notification text. A panel that covers content needs
     to be opaque enough to be read; the blur stays for depth. */
  .alertcard{position:absolute;top:64px;right:var(--gut-card);left:var(--gut-card);z-index:55;border-radius:var(--r-lg);padding:15px 16px;
    background:rgba(14,21,30,.97);transform:translateY(-8px);opacity:0;visibility:hidden;transition:all .3s var(--ease);max-height:72dvh;overflow-y:auto}
  .alertcard.open{transform:none;opacity:1;visibility:visible}
  .alertcard h5{font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2);margin-bottom:8px;display:flex;justify-content:space-between;align-items:center}
  .alertcard h5 .nf-note{letter-spacing:.02em;font-weight:500;text-transform:none;flex:1;margin-left:8px;opacity:.7}
  .nf{display:flex;gap:11px;align-items:flex-start;padding:10px 2px;border-top:1px solid var(--line);cursor:pointer}
  .nf:active{opacity:.7}
  .nf-ic{flex:0 0 auto;width:32px;height:32px;border-radius:var(--r-sm);display:flex;align-items:center;justify-content:center;font-size:15px}
  .nf-ic.brief{background:rgba(240,163,78,.14);color:var(--warn)}
  .nf-ic.act{background:rgba(232,255,61,.12);color:var(--accent)}
  .nf-ic.cond{background:rgba(77,255,200,.1);color:var(--accent-2)}
  .nf-ic.safe{background:rgba(255,91,60,.13);color:#ff5b3c}
  .nf-ic.soc{background:rgba(120,160,255,.12);color:#8ab0ff}
  .nf-tx{min-width:0}
  .nf-cat{font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2)}
  .nf-tx p{margin:3px 0 0;font-size:12px;line-height:1.45;color:var(--text-2)}
  .nf-tx p b{color:var(--text);font-weight:600}
  .alertcard h5 button{background:none;border:none;color:var(--text-2);font-size:16px;width:36px;height:36px;display:grid;place-items:center;margin:-8px -8px -8px 0;border-radius:var(--radius-pill)}
  .alertcard .a{display:flex;gap:10px;align-items:flex-start;font-size:13px;line-height:1.5}
  .alertcard .a i{color:var(--accent);font-size:17px;flex:0 0 auto;margin-top:1px}
  .alertcard .a b{color:var(--text)}
  .bellbadge{position:absolute;top:4px;right:4px;width:8px;height:8px;border-radius:50%;background:var(--accent)}
  .ridepick{display:flex;gap:8px;flex-wrap:wrap;margin:0 var(--gut) 16px}
  /* ============ LOCAL PARTNERS ============ */
  .lrep{margin:0 var(--gut) 18px;padding:14px 15px 12px;border-radius:var(--r-lg);cursor:pointer;border:1px solid rgba(77,255,200,.22)}
  .lr-top{display:flex;align-items:center;gap:10px;margin-bottom:10px}
  .lr-top img{width:38px;height:38px;border-radius:50%;object-fit:cover;flex:0 0 auto}
  .lr-id{flex:1;min-width:0}
  .lr-id b{display:block;font-size:13px;font-weight:700}
  .lr-id span{display:block;font-size:10px;color:var(--accent-2);margin-top:1px}
  .lr-top time{font-size:10px;color:var(--text-2);flex:0 0 auto}
  .lrep p{font-size:13px;line-height:1.55;color:var(--text);margin:0;display:flex;gap:9px;align-items:flex-start}
  .lrep p .yvp{flex:0 0 auto;margin-top:1px}
  .lr-foot{display:flex;align-items:center;gap:6px;margin-top:10px;padding-top:9px;border-top:1px solid var(--line);font-size:10px;color:var(--text-2)}
  .lr-foot i{color:var(--accent-2);font-size:13px}
  .lprow{gap:12px;padding:2px 20px 14px}
  .lp{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;gap:5px;background:none;border:none;color:var(--text);font-family:inherit;cursor:pointer;width:76px}
  .lp-av{position:relative;width:60px;height:60px}
  .lp-av img{width:100%;height:100%;border-radius:50%;object-fit:cover;border:2px solid rgba(77,255,200,.5)}
  .lp-av i{position:absolute;bottom:-2px;right:-2px;width:22px;height:22px;border-radius:50%;background:var(--accent-2);color:#06251c;font-size:11px;display:grid;place-items:center;border:2px solid var(--bg)}
  .lp b{font-size:12px;font-weight:600}
  .lp span{font-size:10px;color:var(--text-2);text-align:center;line-height:1.2}
  .lp-sub{font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2);margin:6px 0 10px}
  .svc{display:flex;align-items:center;gap:12px;padding:13px 15px;border-radius:var(--r-md);margin-bottom:10px;cursor:pointer}
  .svc-ic{width:40px;height:40px;border-radius:var(--r-chip);background:rgba(77,255,200,.1);border:1px solid rgba(77,255,200,.25);display:grid;place-items:center;color:var(--accent-2);font-size:18px;flex:0 0 auto}
  .svc-tx{flex:1;min-width:0}
  .svc-tx b{display:block;font-size:13px;font-weight:700}
  .svc-tx>span{display:block;font-size:11px;color:var(--text-2);line-height:1.45;margin-top:2px}
  .svc-meta{display:flex;gap:10px;margin-top:6px;font-size:10px;font-weight:600;color:var(--accent-2)}
  .svc-go{color:var(--text-2);font-size:16px;flex:0 0 auto}
  /* ============ PARTNER PROFILE ============ */
  .p-hero{padding:18px 20px 20px}
  .p-id{display:flex;gap:15px;align-items:center}
  .p-av{position:relative;flex:0 0 auto}
  .p-av img{width:88px;height:88px;border-radius:28px;object-fit:cover;display:block;border:1px solid var(--line)}
  .p-av i{position:absolute;bottom:-4px;right:-4px;width:26px;height:26px;border-radius:50%;background:var(--accent-2);color:#06251c;font-size:14px;display:grid;place-items:center;border:2.5px solid var(--bg)}
  .p-meta{min-width:0}
  .p-meta h1{font-size:21px;font-weight:800;letter-spacing:-.01em}
  .p-role{display:inline-flex;align-items:center;gap:6px;margin-top:5px;font-size:11px;font-weight:700;letter-spacing:.06em;color:var(--accent-2);background:rgba(77,255,200,.08);border:1px solid rgba(77,255,200,.3);border-radius:var(--radius-pill);padding:4px 11px}
  .p-spot{display:flex;align-items:center;gap:5px;margin-top:8px;background:none;border:none;color:var(--text-2);font-size:12px;font-family:inherit;padding:0;cursor:pointer}
  .p-spot i{color:var(--accent);font-size:13px}
  .p-stats{display:flex;margin:16px 0 12px;border-radius:var(--r-md);padding:12px 0}
  .p-stats div{flex:1;text-align:center}
  .p-stats div+div{border-left:1px solid var(--line)}
  .p-stats b{display:block;font-size:14px;font-weight:700;color:var(--text)}
  .p-stats b i{color:var(--accent);font-size:13px}
  .p-stats span{display:block;font-size:10px;color:var(--text-2);margin-top:3px}
  .p-langs{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px}
  .p-actions{display:flex;gap:10px}
  .p-actions .cta{flex:1;display:flex;align-items:center;justify-content:center;gap:7px}
  .p-actions .cta.on{background:var(--surface-2);color:var(--accent);border:1px solid rgba(232,255,61,.4);box-shadow:none}
  .p-about{font-size:13px;line-height:1.65;color:var(--text-2)}
  .ptrust{display:flex;gap:11px;align-items:flex-start;margin:0 var(--gut);padding:13px 15px;border-radius:var(--r-md);border:1px solid rgba(77,255,200,.25)}
  .ptrust i{color:var(--accent-2);font-size:20px;flex:0 0 auto;margin-top:1px}
  .ptrust b{display:block;font-size:12px;font-weight:700}
  .ptrust span{display:block;font-size:11px;color:var(--text-2);line-height:1.5;margin-top:2px}
  /* ============ BECOME FLOW ============ */
  .bbar{padding:8px 20px calc(16px + env(safe-area-inset-bottom));background:linear-gradient(transparent,rgba(4,6,8,.92) 32%);position:relative}
  .bbar .cta{width:100%;margin-top:4px}
  .bp-intro{display:flex;gap:12px;align-items:flex-start;margin:14px 20px 18px;padding:14px 16px;border-radius:var(--r-lg);border:1px solid rgba(232,255,61,.22)}
  .bp-intro i{color:var(--accent);font-size:22px;flex:0 0 auto;margin-top:2px}
  .bp-intro p{margin:0;font-size:12px;line-height:1.6;color:var(--text-2)}
  .bp-intro p b{color:var(--text)}
  .ridepick .chip i{font-size:13px}
  .bp-ta{width:100%;border-radius:var(--r-md);padding:14px 15px;font-family:inherit;font-size:13px;color:var(--text);border:1px solid var(--line);background:var(--surface-solid);resize:none;outline:none}
  .bp-note{margin:4px 20px 0;font-size:11px;color:var(--text-2);line-height:1.6}
  .vcheck{margin:0 var(--gut) 16px;border-radius:var(--r-lg);padding:6px 15px}
  .vc{display:flex;align-items:center;gap:12px;padding:12px 0}
  .vc+.vc{border-top:1px solid var(--line)}
  .vc>i{font-size:21px;color:var(--text-2);flex:0 0 auto}
  .vc.done>i{color:var(--accent-2)}
  .vc div{flex:1;min-width:0}
  .vc b{display:block;font-size:13px;font-weight:600}
  .vc span{display:block;font-size:11px;color:var(--text-2);margin-top:2px;line-height:1.4}
  .bp-photo{display:flex;align-items:center;gap:13px;margin:0 var(--gut) 18px;padding:13px 15px;border-radius:var(--r-md);cursor:pointer}
  .bp-ph{width:52px;height:52px;border-radius:var(--r-md);background:var(--surface-2);border:1px dashed rgba(230,238,246,.3);display:grid;place-items:center;color:var(--text-2);font-size:21px;flex:0 0 auto}
  .bp-photo div{flex:1}
  .bp-photo b{display:block;font-size:13px;font-weight:600}
  .bp-photo span{display:block;font-size:11px;color:var(--text-2);margin-top:2px}
  .bp-photo>i{color:var(--accent);font-size:18px}
  .bp-done{padding:44px 24px 20px;text-align:center}
  .bp-done .ok{width:84px;height:84px;border-radius:50%;border:1.5px solid var(--accent);display:grid;place-items:center;color:var(--accent);font-size:36px;margin:0 auto 18px;animation:pop .5s var(--ease) both}
  .bp-done h2{font-size:22px;font-weight:800}
  .bp-done p{font-size:13px;color:var(--text-2);line-height:1.6;margin-top:8px}
  /* ============ PROFILE PARTNER CTA ============ */
  .become{display:flex;align-items:center;gap:12px;margin:0 var(--gut) 16px;padding:14px 16px;border-radius:var(--r-md);cursor:pointer;border:1px solid rgba(232,255,61,.3)}
  .become>i:first-child{color:var(--accent);font-size:22px;flex:0 0 auto}
  .become div{flex:1;min-width:0}
  .become b{display:block;font-size:13px;font-weight:700}
  .become span{display:block;font-size:11px;color:var(--text-2);margin-top:2px;line-height:1.4}
  .become>i:last-child{color:var(--text-2);font-size:16px}
  .become.applied{cursor:default;border-color:rgba(77,255,200,.3)}
  .become.applied>i:first-child{color:var(--accent-2)}
  /* ============ POSTING VISIBILITY ============ */
  /* ============ YOUR SPOTS TODAY ============ */
  /* ===== Phase 8: For You decision list =====
     Hierarchy is enforced by SIZE and COLOUR, in this order:
     verdict pill > spot name > best window > reality > score.
     The score is deliberately the quietest element on the card. */
  .briefbar{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;padding:10px var(--gut) 12px}
  .bb-label{display:flex;align-items:center;gap:6px;font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2)}
  .bb-label i{font-size:13px;color:var(--accent)}
  .bb-count{font-size:15px;font-weight:700;color:var(--text)}

  .decisions{padding:0 var(--gut-card) 4px}
  .dgroup{display:flex;align-items:center;gap:7px;font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;margin:14px 6px 8px;color:var(--text-2)}
  .dgroup:first-child{margin-top:2px}
  .dgroup small{font-weight:700;letter-spacing:0;opacity:.7}
  .dgroup.go{color:var(--accent)}
  .dgroup.maybe{color:var(--warn)}
  .dgroup.toggle{width:calc(100% - 12px);min-height:40px;background:none;border:0;font-family:inherit;cursor:pointer;padding:0;text-align:left}
  .dgroup.toggle i{margin-left:auto;font-size:13px}

  .dcard{border:1px solid var(--line);background:var(--surface-solid);border-radius:var(--r-lg);margin-bottom:9px;overflow:hidden}
  .dcard.go{border-color:rgba(232,255,61,.34)}
  .dcard.open{border-color:rgba(230,238,246,.24)}
  /* PHASE 8.5F: real device bug -- a GO card's lime border turned grey the
     moment it was expanded. Root cause, provable: .dcard.go and .dcard.open
     are equal-specificity two-class selectors, so whichever is declared
     LATER in the sheet wins for a card that is both (.dcard.go.open in the
     DOM, from decisionCard()'s `v + (open ? " open" : "")`) -- .dcard.open
     happens to sit after .dcard.go above, so expansion silently overrode
     the verdict colour. Fixed by SPECIFICITY, not by reordering (a
     reorder would just move the same fragility to whichever rule is
     declared last next time): a three-class .dcard.go.open rule is
     structurally more specific than the two-class .dcard.open, so it wins
     regardless of source order from here on. Verdict semantics now
     provably outrank expansion/selection layout state, the same
     precedence rule Phase 8.5D already established for
     .spot-pin.sel.sd-hazard overriding .spot-pin.sel's lime border. */
  .dcard.go.open{border-color:rgba(232,255,61,.34)}
  .dmain{display:block;width:100%;padding:11px 13px 12px;background:none;border:0;color:var(--text);font-family:inherit;text-align:left;cursor:pointer}
  .dtop{display:flex;align-items:center;gap:11px}
  .dtitle{flex:1 1 auto;min-width:0;display:flex;align-items:baseline;gap:9px;flex-wrap:wrap}
  .dtitle>b{font-size:15px;font-weight:700;letter-spacing:-.01em}
  .dsub{display:flex;align-items:center;gap:8px;margin-top:7px;min-width:0}
  /* 1. verdict — the loudest thing on the card */
  /* .dpill is styled by the shared verdict system near the end of this file. */
  /* 2. spot name  3. best window */
  .dwin{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--accent-2)}
  .dwin i{font-size:11px}
  /* 4. current reality */
  /* .dr is styled by the shared reality system near the end of this file. */
  .dreason{flex:1 1 auto;min-width:0;font-size:11px;color:var(--text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .dthumb{position:relative;flex:0 0 auto;width:44px;height:44px;border-radius:var(--r-sm);overflow:hidden;display:block}
  .dthumb img{width:100%;height:100%;object-fit:cover;display:block}
  .dthumb i{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:15px;color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.6)}
  /* 5. score — present, but never competing with the verdict */
  .dscore{flex:0 0 auto;display:flex;flex-direction:column;align-items:center;line-height:1;color:var(--text-2);font-size:13px;font-weight:700;opacity:.9}
  .dscore small{font-size:8px;letter-spacing:.1em;font-weight:700;margin-top:3px;opacity:.75}
  .dchev{flex:0 0 auto;font-size:13px;color:var(--text-2);transition:transform .2s var(--ease)}
  .dcard.open .dchev{transform:rotate(180deg)}

  /* Expanded panel: REALITY first, forecast one level below it. */
  .ddetail{border-top:1px solid var(--line);padding:4px 14px 13px;animation:ddIn .22s var(--ease)}
  @keyframes ddIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
  .ddetail h6{font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2);margin:12px 0 6px}
  .dd-row{display:flex;gap:8px;align-items:flex-start;font-size:12px;line-height:1.45;margin-bottom:5px}
  .dd-row i{flex:0 0 auto;font-size:14px;color:var(--accent-2);margin-top:1px}
  .dd-row.muted,.dd-row.muted i{color:var(--text-2)}
  .dd-row b{font-weight:700;color:var(--text)}
  .dd-conds{display:flex;flex-wrap:wrap;gap:7px}
  .dd-conds span{display:flex;align-items:center;gap:5px;font-size:11px;font-weight:600;color:var(--text-2);background:rgba(255,255,255,.04);border:1px solid var(--line);padding:5px 9px;border-radius:var(--r-sm)}
  .dd-conds i{font-size:12px}
  .dd-open{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;margin-top:12px;padding:11px;border-radius:var(--r-chip);min-height:44px;border:1px solid var(--line);background:rgba(255,255,255,.04);color:var(--text);font-family:inherit;font-size:12px;font-weight:700;cursor:pointer}
  .dd-open i{font-size:13px}
  .dempty{padding:24px 20px;text-align:center;color:var(--text-2);font-size:13px}

  /* Phase 8.1: live-forecast status, shown beside the verdict it can change. */
  .lfchip{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:800;letter-spacing:.08em;
    padding:4px 9px;border-radius:var(--radius-pill);white-space:nowrap}
  .lfchip i{font-size:11px}
  .lfchip.loading{background:rgba(255,255,255,.07);color:var(--text-2);border:1px solid var(--line)}
  .lfchip.loading i{animation:spin 1.1s linear infinite}
  .lfchip.ready{background:rgba(77,255,200,.13);color:var(--accent-2);border:1px solid rgba(77,255,200,.3)}
  @keyframes spin{to{transform:rotate(360deg)}}

  /* Phase 8.1 header: mode title + profile avatar replace the old top tab bar. */
  .hdr-title{flex:1 1 auto;min-width:0;font-size:16px;font-weight:700;letter-spacing:-.01em;padding-left:2px}
  /* 40px outer target, 30px visible avatar -- same touch size as the icon
     buttons beside it, without a 40px face dominating the header. */
  .avabtn{flex:0 0 auto;width:40px;height:40px;padding:5px;border-radius:50%;overflow:hidden;border:none;
    background:none;cursor:pointer}
  .avabtn img{border-radius:50%;border:1px solid var(--line)}
  .avabtn img{width:100%;height:100%;object-fit:cover;display:block}
  .submodes{display:flex;gap:8px;padding:8px var(--gut) 4px}

  /* ===== PHASE 8.2: QUICK REPORT =====
     Composed to fit the required path in one 388x715 viewport: spot, clip
     proof, all six sizes, all four crowd levels, sharing summary and Share.
     Compactness comes from spacing and grid density, never from shrinking tap
     targets -- every tile stays at least 44px tall. */
  #s-upload .scroll#confirmView{display:flex;flex-direction:column;overflow:hidden}
  .qr{flex:1 1 auto;overflow-y:auto;padding:0 var(--gut-card) 6px;scrollbar-width:none}
  .qr::-webkit-scrollbar{display:none}
  .qr-h{font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2);margin:12px 2px 7px}
  .qr-h em{font-style:normal;font-weight:500;letter-spacing:0;text-transform:none;opacity:.8}

  .qr-spot{display:flex;align-items:center;gap:10px;width:100%;padding:10px 12px;margin-top:2px;border-radius:var(--r-md);min-height:56px;
    border:1px solid var(--line);background:var(--surface-solid);color:var(--text);font-family:inherit;text-align:left;cursor:pointer}
  .qr-spot-l{flex:1 1 auto;min-width:0}
  .qr-spot-l small{display:block;font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2)}
  .qr-spot-l b{display:block;font-size:16px;font-weight:700;letter-spacing:-.01em;margin-top:1px}
  .qr-change{flex:0 0 auto;font-size:11px;font-weight:700;color:var(--accent-2)}
  /* PHASE 8.3: the unknown-spot state. Reached when the composer was opened
     without explicit spot context -- the global + or a deep link -- where SPOT
     genuinely does not know where the report was filmed. It reads as the one
     outstanding answer rather than as an error, and Share is disabled until it
     is answered. No colour is borrowed from the hazard palette: nothing is
     wrong, something is simply not known yet. */
  .qr-spot.needs{border-color:rgba(232,255,61,.42);background:rgba(232,255,61,.06)}
  .qr-spot.needs .qr-spot-l b{color:var(--accent)}
  .qr-spot.needs .qr-change{color:var(--accent)}

  .qr-clip{display:flex;align-items:center;gap:11px;margin-top:9px;padding:8px 12px 8px 8px;border-radius:var(--r-md);
    border:1px solid var(--line);background:var(--surface)}
  .qr-thumb{position:relative;flex:0 0 auto;width:54px;height:54px;border-radius:var(--r-sm);overflow:hidden;cursor:pointer;display:block}
  .qr-thumb img{width:100%;height:100%;object-fit:cover;display:block}
  .qr-thumb i{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:17px;color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.65)}
  .qr-clip-meta{flex:1 1 auto;min-width:0}
  .qr-clip-meta b{display:block;font-size:13px;font-weight:700}
  .qr-clip-meta span{font-size:11px;color:var(--text-2)}
  .qr-retake{flex:0 0 auto;display:flex;align-items:center;justify-content:center;gap:5px;min-height:40px;padding:0 13px;border-radius:var(--radius-pill);
    border:1px solid var(--line);background:rgba(255,255,255,.05);color:var(--text);font-family:inherit;font-size:11px;font-weight:700;cursor:pointer}
  .qr-retake i{font-size:13px}

  .qr-grid6{display:grid;grid-template-columns:repeat(3,1fr);gap:7px}
  .qr-grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:7px}
  .qtile{min-height:46px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
    border-radius:var(--r-chip);border:1px solid var(--line);background:var(--surface-solid);color:var(--text);
    font-family:inherit;font-size:12px;font-weight:800;letter-spacing:.04em;cursor:pointer;transition:all .15s}
  .qtile:active{transform:scale(.97)}
  .qtile.on{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}
  .qtile .em{font-size:14px;line-height:1}
  .qtile .lb{font-size:10px;font-weight:800;letter-spacing:.05em}

  .qr-rows{margin-top:12px;border-top:1px solid var(--line)}
  .qr-row{display:flex;align-items:center;gap:10px;width:100%;padding:12px 2px;background:none;border:0;
    border-bottom:1px solid var(--line);color:var(--text);font-family:inherit;font-size:13px;font-weight:600;text-align:left;cursor:pointer}
  .qr-row span{flex:1 1 auto;min-width:0}
  .qr-row i{flex:0 0 auto;font-size:14px;color:var(--text-2)}
  .qr-more,.qr-sharing{display:none;padding:2px 0 10px}
  .qr-more.open,.qr-sharing.open{display:block}
  .qr-more .ridepick{margin:0 0 10px;gap:7px}
  .qr-fc{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--accent-2);
    background:rgba(77,255,200,.1);border:1px solid rgba(77,255,200,.25);padding:5px 10px;border-radius:var(--radius-pill)}
  .qshare{display:flex;align-items:center;gap:10px;width:100%;padding:11px 12px;margin-bottom:7px;border-radius:var(--r-chip);min-height:52px;
    border:1px solid var(--line);background:var(--surface);color:var(--text);font-family:inherit;text-align:left;cursor:pointer}
  .qshare.on{border-color:var(--accent);background:rgba(232,255,61,.07)}
  .qs-main{flex:1 1 auto;min-width:0}
  .qs-main b{display:block;font-size:13px;font-weight:700}
  .qs-main span{display:block;font-size:11px;color:var(--text-2);line-height:1.35;margin-top:1px}
  .qshare>i{flex:0 0 auto;font-size:17px;color:var(--text-2)}
  .qshare.on>i{color:var(--accent)}

  /* Sticky primary action: always reachable on the normal path. */
  .qr-sticky{flex:0 0 auto;padding:9px var(--gut-card) calc(10px + env(safe-area-inset-bottom));
    border-top:1px solid var(--line);background:rgba(11,17,24,.92);backdrop-filter:blur(12px)}
  .qr-sticky .cta{width:100%}
  .qr-sticky .cta.off{opacity:.42;pointer-events:none}

  /* ============ VERDICT CALL ============ */
  /* PHASE 8.3: .vcall/.vmid removed with the old hero; see .spv-call below. */
  /* PHASE 8.5F: line-height:1 added -- same shared-system audit as .chip/
     .seg button/.dhaz, same missing property, same fix. */
  .vchip{position:relative;flex:0 0 auto;gap:5px;font-size:11px;font-weight:500;color:var(--text-2);background:rgba(255,255,255,.05);border:1px solid var(--line);border-radius:var(--radius-pill);padding:4px 10px}
  .vchip i{font-size:12px;color:var(--accent-2)}
  .vchip.warn{color:#ff7043;border-color:rgba(255,112,67,.4)}
  .vchip.warn i{color:#ff7043}
  /* PHASE 8.3: .score.fy removed; the For You score is .spv-score below. */
  /* ============ FEED VERDICT LINE ============ */
  .fline{display:flex;align-items:center;gap:9px;padding:10px 15px;border-top:1px solid var(--line);font-size:12px;color:var(--text-2);line-height:1.4}
  .fline b{color:var(--accent-2);font-weight:700}
  .fline .yvp{flex:0 0 auto}
  /* ============ BEST WINDOW TIMELINE ============ */
  /* PHASE 8.5F: winTimeline() moved out of the Forecast Support accordion
     into the always-visible Decision Summary (see renderSpot() in app.js)
     -- this is now its ONLY context, so the base margin below (already
     sized for a top-level block, unchanged) is what actually applies.
     box-shadow:none is the same Phase 8.5E fix that used to live on the
     accordion-scoped `.spv-accp .wline` variant (now dead -- .wline never
     renders inside .spv-accp anymore) -- cancels .glass's own inset
     top-highlight so this card keeps ONE edge source (its real border),
     not two. */
  .wline{margin:16px var(--gut) 18px;padding:14px 15px 13px;border-radius:var(--r-md)}
  /* .wline.glass (two classes) rather than relying on .wline alone beating
     .glass by source order -- the SAME specificity-over-ordering precedent
     item 10's .dcard.go.open fix established this pass, applied here too. */
  .wline.glass{box-shadow:none}
  .wline h5{display:flex;align-items:center;font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2)}
  .wline h5 .wl-live{margin-left:auto;font-size:8px;letter-spacing:.1em;color:var(--accent-2);border:1px solid rgba(77,255,200,.3);border-radius:var(--radius-pill);padding:2px 7px}
  /* PHASE 8.5F FINAL COLLISION FIX: a dedicated, always-reserved lane for
     the NOW label, between the header and the hour blocks -- see the
     matching comment in winTimeline() (app.js) for the full root-cause
     history. Its own height is reserved layout space, not an overlay, so
     the label inside it can never overlap the header text above or the
     blocks below regardless of font metrics or a block's own glow bleed.
     Present unconditionally (even with no NOW marker) so the chart's
     height/rhythm does not jump between spots that do and do not have one. */
  .wtl-nowlane{position:relative;height:14px;margin:8px 0 0}
  .wtl-now-label{position:absolute;top:50%;transform:translate(-50%,-50%);
    font-size:7px;font-weight:800;letter-spacing:.06em;color:#fff;white-space:nowrap;
    background:rgba(8,16,24,.78);border:1px solid rgba(255,255,255,.14);padding:1.5px 4px;border-radius:var(--radius-pill)}
  /* Top margin trimmed (12px -> 4px): .wtl-nowlane above now supplies the
     bulk of the header-to-blocks spacing the old flat margin used to. */
  .wtl{position:relative;display:flex;gap:3px;height:26px;margin:4px 0 6px}
  .wtl i{flex:1;border-radius:5px;background:var(--surface-2)}
  .wtl i.go{background:linear-gradient(180deg,var(--accent),#b8d426);box-shadow:0 0 10px rgba(232,255,61,.22)}
  .wtl i.fade{background:rgba(240,163,78,.45)}
  /* PHASE 8.5 REAL-DEVICE CORRECTION: spot-local NOW, clock position only.
     A static line, deliberately: it marks where the current spot-local hour
     falls on the AUTHORED chart, not a live evaluation of anything.

     PHASE 8.5.1: solid, not dashed. On an actual iPhone a 1.5px dashed border
     over a ~26px span renders as two or three short strokes -- close enough
     together to read as a stray dot, not a line.

     PHASE 8.5B: still read as a dot on-device even solid -- a 1.5px hairline
     BORDER can visually thin out or anti-alias away on some mobile displays,
     especially at sub-pixel positions from the percentage-based `left` this
     is placed at. Switched from a border-left trick to an actual filled
     `width` bar (kept only in history below via git blame from here on --
     the markup and rule both changed again in 8.5D, see next).

     PHASE 8.5D: STILL not clearly visible on a real iPhone, again. Root
     cause this time, provable from source rather than guessed: `.wtl` (the
     bar) lives inside FORECAST SUPPORT, a `.spv-accp`/`.spv-accinner`
     accordion panel whose `overflow:hidden` is LOAD-BEARING for its
     grid-rows(0fr->1fr) open/close animation and cannot be removed --
     which makes ANY element that depends on extending past its own parent
     box (as the previous `top:-7px;bottom:-7px` overhang did) fragile by
     construction, not just here but for any WEB-KIT rendering quirk in
     between. The redesign below never relies on overhang: the line now
     fills `.wtl`'s OWN 26px height exactly (top:0;bottom:0, zero negative
     offset), so nothing about it can be clipped by an ancestor no matter
     how tightly it hugs its own box. A SEPARATE, provable contrast defect
     was also fixed in passing: `.wtl i.go` is a bright `--accent`
     (`#e8ff3d`) yellow-green gradient, and a plain white line crossing a GO
     block had low luminance contrast against it -- fixed with a thin dark
     halo (a 1px near-black box-shadow ring) around the white fill, which
     keeps it legible against ANY hour-block colour, not just the darker
     ones. z-index is now explicit rather than relying on flex/positioned
     painting-order rules holding across browsers. The label moved to its
     own dedicated child (`.wtl-now-label`, with its own background chip for
     the same any-colour-behind-it contrast reason) instead of a bare
     unstyled `<span>`; the line is its own dedicated child too
     (`.wtl-now-line`) rather than the wrapper doing double duty as both
     positioning anchor and painted line -- exactly the "dedicated overlay
     element, not a border trick" structure asked for. */
  /* PHASE 8.5F FINAL VISUAL POLISH, item 1: still not clearly visible on a
     real iPhone, a FOURTH time. Root cause this time was the CONTRAST
     technique, not clipping or positioning (those are unchanged, again):
     the line's edge treatment combined a semi-transparent (55% opacity)
     dark ring with a BLURRED white glow (`0 0 4px`). A blur softens an
     edge by definition; combined with a not-fully-opaque ring, the result
     reads as a soft halo/outline rather than a bold filled bar, especially
     against the bright GO block where the white fill itself has almost no
     luminance contrast and only the (weak, semi-transparent) ring was ever
     doing any work. Fixed by making the contrast edge fully opaque and
     removing the blur entirely -- a crisp, solid ring, no softness -- so
     the line reads as filled against both dark AND bright/lime sections,
     never merely outlined. Width, position, and the underlying spot-local
     time math are untouched. */
  /* PHASE 8.5F FINAL POLISH ROUND 2, item D: STILL not immediately
     noticeable, plus a NEW real-device finding -- the label collided
     visually with the coloured hour blocks it floats above. Root cause of
     the collision, provable from the box model: the label sat at
     `bottom:100%` of a zero-height wrapper (i.e. just above the block
     row), but `.wtl i.go` carries its own `box-shadow:0 0 10px` GO-glow
     (below, unchanged) which bleeds 10px in every direction including
     upward -- well past the 3px gap the label used to sit at, so the
     label rendered inside the glow's own bleed zone. Widening that gap to
     8px only narrowed the collision window, it did not close it.

     PHASE 8.5F FINAL COLLISION FIX: the label is no longer positioned
     relative to `.wtl` (the block row) AT ALL -- see `.wtl-nowlane` above,
     a dedicated reserved-space sibling between the header and `.wtl`. The
     `.wtl-now` zero-width wrapper this used to need is gone with it: the
     line is now a direct, independently positioned child of `.wtl` itself
     (which is already `position:relative`), so it needs no wrapper to
     anchor to. Line weight (2.5px core, fully opaque ring) is unchanged
     from the immediately prior round. */
  .wtl-now-line{position:absolute;top:0;bottom:0;width:2.5px;transform:translateX(-50%);
    z-index:5;pointer-events:none;background:#fff;box-shadow:0 0 0 1px rgba(0,0,0,1)}
  /* item D (this round): the out-of-range treatment was bare small grey
     text with no container at all -- a completely different (much weaker)
     visual language than the in-range label's filled pill, so "obvious"
     visibility dropped to near zero the moment a spot's local time fell
     outside the authored 06-18 chart. Given the SAME filled-pill treatment
     (background/border/radius) as .wtl-now-label so presence does not
     depend on which of the two states is showing. Text and the underlying
     "outside chart" logic are unchanged. */
  .wtl-outside{display:inline-flex;align-items:center;gap:5px;margin-top:8px;
    font-size:10px;font-weight:700;letter-spacing:.03em;color:#fff;white-space:nowrap;
    background:rgba(8,16,24,.78);border:1px solid rgba(255,255,255,.14);padding:3px 8px;border-radius:var(--radius-pill)}
  .wtl-outside small{opacity:.75;margin-left:2px;font-weight:500}
  /* PHASE 8.3: .wnow removed with the mock NOW marker it drew. It positioned a
     "current time" line from the prototype's hardcoded 9:41 screen clock. A real
     one requires spot-local time semantics, which do not exist yet. */
  .wtick{display:flex;justify-content:space-between;font-size:10px;color:var(--text-2);letter-spacing:.05em}
  .wsay{font-size:12px;color:var(--text-2);margin-top:10px;line-height:1.5}
  .wsay b{color:var(--accent-2);font-weight:700}
  /* ============ CROWD / SECRET / LOCAL KNOWLEDGE ============ */
  .crowdrow{display:flex;align-items:center;gap:8px;padding:9px 15px;border-top:1px solid var(--line);font-size:11px;color:var(--text-2)}
  .crowdrow .cb{display:inline-flex;align-items:center;gap:6px;font-weight:600;color:var(--text);font-size:12px;white-space:nowrap}
  /* PHASE 8.3: .crowdbig removed; crowd renders as .spv-crowd in Reality Check. */
  .lk{border-radius:var(--r-md);padding:15px 16px;font-weight:300}
  .lk p{font-size:13px;line-height:1.6;color:var(--text)}
  .lk .by{font-size:11px;color:var(--text-2);margin-top:8px;display:flex;align-items:center;gap:5px}
  .lk .by i{color:var(--accent)}
  .lk .hist{font-size:12px;color:var(--text-2);margin-top:12px;padding-top:12px;border-top:1px solid var(--line);line-height:1.55}
  .lk .hist b{color:var(--text);font-weight:600}
  .lk .vibe{margin-top:12px;padding:11px 13px;border-radius:var(--r-chip);background:rgba(232,255,61,.08);border:1px solid rgba(232,255,61,.22);font-size:12px;line-height:1.55;color:var(--text)}
  .lk .entry{font-size:12px;color:var(--text-2);margin-top:12px;line-height:1.5}
  .lk .entry b{color:var(--text);font-weight:600}
  /* PHASE 8.4: .secretbox removed with the false "Protected Spots" card. */
  .sectoggle{display:flex;align-items:center;gap:12px;margin:0 var(--gut) 18px;padding:13px 16px;border-radius:var(--r-md)}
  .sectoggle .inf{flex:1}
  .sectoggle b{font-size:13px;font-weight:600;display:block}
  .sectoggle span{font-size:11px;color:var(--text-2)}
  .switch{width:46px;height:27px;border-radius:var(--radius-pill);background:var(--surface-2);border:1px solid var(--line);position:relative;transition:background .2s;flex:0 0 auto}
  .switch::after{content:"";position:absolute;top:2px;left:2px;width:21px;height:21px;border-radius:50%;background:var(--text-2);transition:all .2s var(--ease)}
  .switch.on{background:var(--accent);border-color:var(--accent)}
  .switch.on::after{left:21px;background:var(--accent-ink)}
  /* ============ SESSIONS ============ */
  .sess{margin:0 var(--gut) 12px;border-radius:var(--radius-card);padding:15px 16px;display:block;width:calc(100% - 40px);text-align:left;color:var(--text);cursor:pointer}
  .sess .top{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
  .sess .top b{font-size:15px;font-weight:600}
  .sess .top span{font-size:11px;color:var(--text-2)}
  .sess .nums{display:flex;gap:18px}
  /* PHASE 8.4: the session score was inline-styled accent yellow. In this
     product accent means GO -- the call -- and a past session's score is not a
     call about anything. Same figure, same prominence within the row, one less
     thing wearing the verdict's colour. Nothing about the mock metrics changed;
     they remain prototype content. */
  .sess .nums div b{font-size:15px;font-weight:700;color:var(--text)}
  .sess .nums div span{font-size:10px;color:var(--text-2);display:block;letter-spacing:var(--track-eyebrow);font-weight:800;text-transform:uppercase;margin-top:2px}
  .streak{display:flex;gap:8px;align-items:center;margin:0 var(--gut) 16px;padding:13px 15px;border-radius:var(--r-md);font-size:13px}
  .streak i{color:var(--accent);font-size:17px}

  /* PHASE 8.5 REAL-DEVICE CORRECTION: the compact hazard chip. Shown beside
     -- never instead of -- the verdict pill, on every surface that exposes a
     genuine Hazard. See decisionCard() (For You) and spotCallParts() (Map
     peek/expand) in app.js / discover-map.js. Positioned OUTSIDE the Phase
     8.3 Spot View CSS block (below): it is not Spot-View-specific, and
     scripts/check-spotview-hierarchy.js asserts that block contains no
     motion of its own. */
  /* PHASE 8.5F: line-height:1 added -- same shared pill-centering audit as
     .chip/.seg button/.vchip. */
  .dhaz{position:relative;gap:4px;flex:0 0 auto;
    font-size:10px;font-weight:800;letter-spacing:.06em;
    color:#ff7043;background:rgba(255,112,67,.14);border:1px solid rgba(255,112,67,.4);
    border-radius:var(--radius-pill);padding:4px 8px}
  .dhaz i{font-size:11px}

  /* =====================================================================
     PHASE 8.5.1 — FINAL IPHONE MOTION FEEL CORRECTION

     Direct human iPhone Safari review reversed part of the prior pass and
     asked for a genuinely different hierarchy:

     HAZARD IS CONTINUOUS AGAIN, ON PURPOSE. The previous correction bounded
     Hazard to a one-shot per session because an earlier pass had made GO
     infinite too, and the fix at the time was "nothing loops forever". On an
     actual device that read as "mostly a static outline" -- indistinguishable
     from a spot with no hazard at all after the first few seconds, which is
     exactly wrong for a safety cue. Hazard is the ONE state this product
     explicitly wants to keep seeking attention for as long as it is active;
     GO is not. `hazardPulseClass()` and the `.hzpulse` gate it drove are
     gone -- the six Hazard surfaces below animate unconditionally again,
     the same way they did before that one-shot experiment, but with a
     redesigned keyframe: a smooth breathe between opacity .45 and 1 (never
     fading toward invisible, so the resting midpoint stays legible, which is
     what "too weak" was actually describing) rather than the previous
     expand-and-fade ring. Slow (1.8s), no rotation, minimal scale (1.08 at
     peak) -- attention without flashing.

     GO GOES THE OPPOSITE DIRECTION: from "no animation at all" to "exactly
     one animation, scoped to exactly one moment" -- entering a Spot View
     whose CURRENT verdict is GO. This is deliberately not the "just became
     GO" transition-tracking the previous pass declined to build (still
     disproportionate for a decorative signal); it is simpler than that. The
     signal is ENTRY, not TRANSITION, and Spot View already distinguishes the
     two: renderSpot() with no opts is only ever called from a fresh
     openSpot() navigation, and both reactive re-renders (a live snapshot
     landing, a durable-report read completing) explicitly pass
     `{ preserveScroll: true }`. `!preserveScroll` IS "this is a fresh entry"
     -- no separate session-tracking Set needed, no historical per-spot state
     at all. A live update that flips this same spot GO -> MAYBE while the
     user stays on the screen rebuilds `spotBody.innerHTML` from scratch on
     the reactive path, which never carries the `.go-enter` class regardless
     of the new verdict -- the animated node is destroyed and replaced with a
     plain static one in the same stroke that updates the truth. Nothing to
     cancel; state and truth both win by construction, not by an explicit
     backstop. Scoped to `.spv-call.go .vpill` ONLY -- Spot View's hero pill,
     never `.dpill` (For You), `.ss-v`/`.spot-pin` (Discover) or `.yvp`
     (Reports): GO must read as fully static everywhere except the one
     moment of arriving on its own decision screen.

     Neither needs to suppress the other: GO's is one-shot and finishes in
     well under a second, Hazard's is the only animation that persists, so
     there is never a moment with two competing CONTINUOUS cues on one unit.

     Durations stay deliberately OUTSIDE the 130/200/260 interaction
     vocabulary and this block stays positioned before both the Spot View CSS
     block and the interaction-vocabulary block, for the same reasons as
     before: this is an attention signal, not navigation motion. */
  /* PHASE 8.5B: both keyframes below turned out to be too subtle on an actual
     iPhone -- "praktisch nicht wahrnehmbar" (GO) and "zu schwach ... nur wie
     eine Linie" (Hazard). Redesigned, not just retimed:

     HAZARD moved from a thin animated OUTLINE to an actual breathing GLOW
     (a box-shadow blur layered onto the existing ring), a thicker border
     (2px, was 1.5px) sitting further out (-4px, was -3px), a brighter
     resting state (opacity .55, was .45 -- "in Ruhe klar erkennbar
     bleiben") and a slightly faster cycle (1.6s, was 1.8s). Still opacity/
     transform/box-shadow only -- no rotation, no spin, no strobe.

     GO's halo grew (peak opacity .55->.85, peak scale 1.12->1.3, radius
     -6px->-10px, duration 650ms->800ms so it has time to register) and
     gained a SECOND effect: a brief box-shadow ring directly on the pill
     itself (`.vpill.go-enter`, not just its ::before halo), because a glow
     sitting entirely BEHIND a small pill was easy to miss even at higher
     opacity -- an outline pulse on the pill itself is far harder not to
     notice. Both effects share the one-shot ENTRY scoping this pass already
     established; neither is new architecture, just louder. */
  /* PHASE 8.6 HAZARD REFINEMENT, then PHASE 8.5D REAL-DEVICE CORRECTION.
     hzAttn (Phase 8.5B, kept only in history via git blame) drew a SEPARATE
     ring pseudo-element 3-4px outside each host's own box, animating its
     border/box-shadow -- an "expanding outline" look real feedback called
     noisy, on top of a card that also carried a left accent strip (removed,
     see .hazard above). Phase 8.6 replaced it with a pulse on the icon
     glyph directly; direct iPhone review of THAT found it "no perceptible
     improvement" -- too subtle to register, not a ring but not visible
     either. Nothing below draws a ring outside any element's box, on either
     pass; what changed in 8.5D is amplitude and WHERE the motion lives:

     - The large Spot View card keeps an ICON-only pulse (.hazard .sign i)
       -- it is the one surface with room for a dedicated, generously-sized
       icon, so the icon itself carries the signal, boosted with both scale
       AND a brightness lift (a small icon's opacity/scale delta alone is
       easy to miss; brightness reads instantly even on a small glyph).
     - Compact pills/badges (.dhaz, .vchip.warn, .media .hazbadge) switch
       from an 11-13px icon-only pulse (too small a scale delta in absolute
       pixels to register) to a WHOLE-COMPONENT pulse -- the entire pill
       breathes at a very restrained scale, which moves far more visible
       area than scaling the tiny glyph alone did.
     - The two shape-only hosts (no icon glyph at all) keep a shape pulse:
       the selected map pin, and -- new this pass -- the plain unselected
       round map dot (.spot-dot.sd-hazard), which previously had no hazard-
       specific motion at all. Both stay glow-free on their own (a glowing
       solid dot/circle reads as the removed ring); the dot's EXISTING
       static halo (box-shadow, present since Phase 8, unrelated to this
       refinement) is allowed to breathe in intensity since it stays tight
       to the dot itself -- never a separate offset ring. */
  @keyframes hzIconPulse{
    0%,100%{opacity:.82;transform:scale(1);filter:brightness(1) drop-shadow(0 0 0 rgba(255,91,60,0))}
    50%{opacity:1;transform:scale(1.07);filter:brightness(1.4) drop-shadow(0 0 6px rgba(255,91,60,.6))}
  }
  .hazard .sign i{display:inline-block;animation:hzIconPulse 1.7s ease-in-out infinite}
  /* Whole-pill pulse: scale stays inside the 1.02-1.025 range asked for --
     large enough to register on a compact chip, restrained enough that the
     pill never looks like it is jumping. Local shadow only (no offset). */
  @keyframes hzPillPulse{
    0%,100%{opacity:.88;transform:scale(1);box-shadow:0 0 0 0 rgba(255,91,60,0)}
    50%{opacity:1;transform:scale(1.025);box-shadow:0 0 6px 1px rgba(255,91,60,.35)}
  }
  .dhaz,.vchip.warn,.media .hazbadge{animation:hzPillPulse 1.7s ease-in-out infinite}
  /* Shape-only hosts: opacity/scale (+ the dot's own pre-existing local
     glow modulating in intensity), deliberately no NEW glow added. */
  @keyframes hzShapePulse{
    0%,100%{opacity:.75;transform:scale(1)}
    50%{opacity:1;transform:scale(1.05)}
  }
  .story.hz .ring::before{animation:hzShapePulse 1.8s ease-in-out infinite}
  /* .spot-pin and .spot-dot each already carry a static positioning
     transform (translate, for their map offset); each pulse's scale must
     compose with it in its OWN keyframe, or the animation would silently
     replace that translate every frame and the marker would visibly jump
     on the map. */
  @keyframes hzPinPulse{
    0%,100%{opacity:.78;transform:translate(-8px,-50%) scale(1)}
    50%{opacity:1;transform:translate(-8px,-50%) scale(1.06)}
  }
  .spot-pin.sel.sd-hazard{animation:hzPinPulse 1.7s ease-in-out infinite}
  @keyframes hzDotPulse{
    0%,100%{opacity:.85;transform:translate(-50%,-50%) scale(1);
      box-shadow:0 0 0 1px rgba(255,91,60,.55),0 0 9px rgba(255,91,60,.5)}
    50%{opacity:1;transform:translate(-50%,-50%) scale(1.14);
      box-shadow:0 0 0 1px rgba(255,91,60,.75),0 0 13px rgba(255,91,60,.8)}
  }
  .spot-dot.sd-hazard{animation:hzDotPulse 1.8s ease-in-out infinite}

  @keyframes goEnter{
    0%{opacity:0;transform:scale(.85)}
    30%{opacity:.85;transform:scale(1.3)}
    100%{opacity:0;transform:scale(1.05)}
  }
  .spv-call.go .vpill.go-enter::before{
    content:"";position:absolute;inset:-10px;border-radius:inherit;pointer-events:none;z-index:-1;
    opacity:0;background:radial-gradient(closest-side,rgba(232,255,61,.6),rgba(232,255,61,0) 70%);
    animation:goEnter 800ms cubic-bezier(.22,1,.36,1) 1}
  @keyframes goRing{
    0%{box-shadow:0 0 0 0 rgba(232,255,61,.7)}
    40%{box-shadow:0 0 0 8px rgba(232,255,61,0)}
    100%{box-shadow:0 0 0 8px rgba(232,255,61,0)}
  }
  .spv-call.go .vpill.go-enter{animation:goRing 800ms cubic-bezier(.22,1,.36,1) 1}

  @media (prefers-reduced-motion: reduce){
    .hazard .sign i,.dhaz,.vchip.warn,.media .hazbadge,.story.hz .ring::before,.spot-pin.sel.sd-hazard,.spot-dot.sd-hazard,
    .spv-call.go .vpill.go-enter::before,.spv-call.go .vpill.go-enter{
      animation:none}
  }

  /* ===== PHASE 8.3: SPOT VIEW DECISION HIERARCHY =====
     One visual language, in the order the screen answers questions:
     DECISION -> REALITY -> WHY -> FORECAST SUPPORT -> PRACTICAL -> deeper.
     No transitions are declared here beyond the ones the app already had:
     motion is a dedicated later pass, and scattering ad-hoc easing through a
     restructure is exactly how a design system stops being one. */

  /* --- 1. DECISION HERO --- */
  .spv-hero{padding:6px var(--gut) 14px}
  .spv-hero h1{font-size:29px;font-weight:700;letter-spacing:-.02em;line-height:1.05}
  .spv-sub{font-size:12px;color:var(--text-2);margin-top:3px}
  .spv-call{margin-top:12px;padding:13px 14px;border-radius:var(--r-lg);border:1px solid var(--line);background:var(--surface-solid)}
  .spv-call.go{border-color:rgba(232,255,61,.32)}
  .spv-call.maybe{border-color:rgba(240,163,78,.28)}
  .spv-crow{display:flex;align-items:center;gap:12px}
  .spv-cmid{flex:1 1 auto;min-width:0}
  /* Label above value: on a 388px screen "BEST WINDOW 06:30 - 10:00" on one
     line wrapped mid-time and read as two competing facts. */
  .spv-win{font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:700;color:var(--text-2)}
  .spv-win i{font-size:12px;color:var(--accent-2);vertical-align:-1px;margin-right:5px}
  .spv-win b{display:block;font-size:15px;letter-spacing:-.01em;font-weight:700;color:var(--text);margin-top:3px}
  .spv-win.none{color:var(--text-2);opacity:.85}
  /* PHASE 8.4 FINAL CORRECTION: .spv-rline removed.
     The hero carried a reality chip ("FRESH CLIP · 23 min") and REALITY CHECK
     restated it verbatim two rows below. One fact, twice, in the same viewport.
     REALITY CHECK is the single owner of the evidence line at this level; the
     hero keeps identity, verdict, window, score and the live/hazard flags. */
  /* Score: present, never louder than the call it supports. */
  .spv-score{flex:0 0 auto;text-align:center;line-height:1}
  .spv-score b{display:block;font-size:20px;font-weight:700;color:var(--accent)}
  .spv-score small{display:block;font-size:8px;letter-spacing:var(--track-eyebrow);font-weight:700;color:var(--text-2);margin-top:4px}
  .spv-flags{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px;padding-top:10px;border-top:1px solid var(--line)}

  /* --- section shell, shared by REALITY and PRACTICAL --- */
  .spv-sec{padding:0 var(--gut);margin-bottom:16px}
  .spv-h{font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text-2);margin-bottom:9px}
  .spv-h small{font-size:11px;letter-spacing:0;font-weight:500;text-transform:none;opacity:.85;margin-left:7px}

  /* --- 2. REALITY CHECK --- */
  .spv-rrow{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:11px}
  .spv-rnote{font-size:11px;color:var(--text-2)}
  /* Full-bleed rail: cancel the section's 20px inset and let .clips keep its
     own padding, so scroll-snap still aligns to the first thumbnail. Overriding
     that padding here made the rail auto-scroll 20px on render. */
  .spv-clips{margin:0 calc(var(--gut) * -1);scroll-padding-left:var(--gut)}
  /* scroll-padding-left matches .clips' own 20px inset. Without it the x-axis
     scroll-snap aligns the first thumbnail's edge to the container's scroll
     origin, so the rail renders already scrolled 20px and clips the first
     frame. Presentational only -- .clips itself is untouched. */
  /* One shape for the evidence slot, whichever state it is in: a fresh scoped
     clip, or the last available photo when there is none. */
  .spv-old,.spv-proof{display:flex;align-items:center;gap:12px;width:100%;padding:10px;border-radius:var(--r-md);
    background:var(--surface-solid);border:1px solid var(--line);color:var(--text);font-family:inherit;text-align:left}
  .spv-proof{cursor:pointer;border-color:rgba(77,255,200,.22)}
  .spv-proof:active{background:var(--surface-2)}
  .spv-proof .spv-oimg{position:relative}
  .spv-proof .spv-oimg i{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
    font-size:17px;color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.6)}
  .spv-proof>i{margin-left:auto;flex:0 0 auto;font-size:15px;color:var(--text-2)}
  .spv-proof .spv-otx b{color:var(--accent-2)}
  .spv-oimg{flex:0 0 auto;width:88px;height:60px;border-radius:var(--r-sm);overflow:hidden;display:block}
  .spv-oimg img{width:100%;height:100%;object-fit:cover;display:block}
  .spv-otx{min-width:0;font-size:12px;color:var(--text-2);line-height:1.45}
  .spv-otx b{display:block;font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:700;color:var(--text);margin-bottom:3px}
  .spv-crowd{display:flex;align-items:center;gap:12px;margin-top:11px;padding:12px 14px;border-radius:var(--r-md);
    background:var(--surface-solid);border:1px solid var(--line)}
  .spv-crowd .em{font-size:18px;flex:0 0 auto}
  .spv-crowd b{font-size:10px;letter-spacing:var(--track-eyebrow);font-weight:700;display:block}
  .spv-crowd span{font-size:12px;color:var(--text-2);display:block;margin-top:2px}
  .spv-react{display:flex;gap:8px;margin-top:11px}

  /* --- 3/4. PROGRESSIVE DISCLOSURE ---
     PHASE 8.5F FINAL POLISH ROUND 2, item C: Forecast Support (and WHY THIS
     CALL?, the other .spv-acc) read as washed out/disabled -- not the LABEL
     text itself (.spv-acct is already color:var(--text), full brightness),
     but the CONTAINER: unlike every .glass card elsewhere in the app, .spv-
     acc had a flat opaque background and a border with no companion inset
     highlight, so it read visually flatter than its neighbours instead of
     as an intentional, secondary card. Added the same inset top highlight
     .glass cards use for edge definition -- background/border VALUES and
     the section's secondary role/priority are unchanged; this is purely an
     edge-definition fix, not a promotion in visual weight relative to the
     decision hero above it. */
  .spv-acc{margin:0 var(--gut) 12px;border:1px solid var(--line);border-radius:var(--r-md);background:var(--surface-solid);overflow:hidden;box-shadow:inset 0 1px 0 var(--glass-hi)}
  .spv-acch{display:flex;align-items:center;gap:10px;width:100%;padding:14px 15px;background:none;border:0;
    color:var(--text);font-family:inherit;text-align:left;cursor:pointer}
  .spv-acct{flex:1 1 auto;min-width:0;font-size:11px;letter-spacing:var(--track-eyebrow);font-weight:800;color:var(--text)}
  .spv-acct em{font-style:normal;font-size:11px;letter-spacing:0;font-weight:500;text-transform:none;color:var(--text-2);margin-left:8px}
  .spv-chev{flex:0 0 auto;font-size:14px;color:var(--text-2)}
  .spv-acc.open .spv-chev{transform:rotate(180deg)}
  .spv-accp{display:none;padding:0 15px 14px;border-top:1px solid var(--line)}
  .spv-acc.open .spv-accp{display:block}
  .spv-why{font-size:13px;line-height:1.55;color:var(--text);margin-top:13px}
  .spv-fit{margin-top:12px;padding-top:12px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:8px}
  .spv-frow{display:flex;gap:9px;align-items:flex-start;font-size:12px;line-height:1.45;color:var(--text-2)}
  .spv-frow i{flex:0 0 auto;font-size:15px;color:var(--accent-2);margin-top:1px}
  .spv-frow b{color:var(--text);font-weight:700}
  /* Inside a panel the moved blocks lose their outer page margins. */
  /* PHASE 8.5E: real iPhone review found this card cluster's edges reading
     as doubled/offset. Root cause, provable from source: .glass (used by
     all three cards below) draws its edge from TWO sources on the SAME
     top boundary -- an actual `border` (1px, --line) AND a separate
     `box-shadow:inset 0 1px 0 var(--glass-hi)` (a 1px near-white top
     highlight, sitting one pixel inside that same border). Two different,
     both very faint, near-white/grey 1px lines stacked immediately next to
     each other at the same edge is exactly the "same boundary produced by
     more than one source" defect, and is the kind of thing Safari's
     sub-pixel rasterisation makes read as a soft double-stroke rather than
     two crisp, obviously-separate lines. Scoped to ONLY the cards that
     actually show it (not .glass generally, which is used broadly across
     the app and works as an intentional "glass sheen" effect elsewhere)
     -- each keeps its ONE real border from .glass as its sole edge
     treatment; only the redundant inset highlight is removed here.
     PHASE 8.5F: .wline's OWN box-shadow:none fix moved to the unscoped
     `.wline.glass` rule above (BEST WINDOW TIMELINE) now that winTimeline()
     no longer ever renders inside .spv-accp -- this was the third card of
     the original three; the other two (Swell/Wind/Tide/Water and the live
     forecast status block) are still genuinely inside this accordion, so
     they keep their scoped selectors here. */
  .spv-conds{margin-top:13px;box-shadow:none}
  .spv-accp .noreport{margin:12px 0 0 !important;text-align:left;display:block;box-shadow:none}
  .spv-accp .noreport i{margin-right:6px}

  /* --- 5. KNOW BEFORE YOU PADDLE OUT --- */
  .spv-know{margin-top:0;padding:14px 15px;border-radius:var(--r-md);display:flex;flex-direction:column;gap:12px}
  .spv-sec .hazard{margin:0 0 12px}
  .spv-krow{display:flex;gap:10px;align-items:flex-start;font-size:12px;line-height:1.5;color:var(--text-2)}
  .spv-krow i{flex:0 0 auto;font-size:16px;color:var(--accent-2);margin-top:1px}
  .spv-krow b{color:var(--text);font-weight:700;font-size:12px;letter-spacing:.02em}
  .spv-krow.warn i{color:var(--warn)}

  /* ===== PHASE 8.4: ONE VERDICT SYSTEM, ONE REALITY SYSTEM =====

     THE PROBLEM THIS REPLACES
     GO / MAYBE / NOPE was styled four separate times -- .dpill on For You,
     .vpill inside .spv-call on Spot View, .ss-v in the Discover sheet, and
     .yvp in the Reports feed and the partner report. They disagreed on size
     (13 / 11 / 10px), tracking (.1em / .09em), padding and border, and .yvp
     had NO base rule at all: `<span class="yvp go">GO</span>` rendered as
     plain unstyled text. The verdict is the single most important signal in
     the product and it looked different on every screen it appeared on.

     THE RULE
     Colour and weight are keyed ONLY to the verdict, never to the screen.
     Size is the one thing that varies, and it varies in three deliberate
     steps: hero, card, inline. Nothing here changes what a verdict IS --
     verdictFor() remains the single source. */
  /* display/align-items/justify-content/line-height/white-space now come
     from the one shared pill-centering contract near .chip; this rule owns
     only what genuinely varies for the verdict-pill family. */
  .spv-call .vpill,.dpill,.ss-v,.yvp{
    position:relative;flex:0 0 auto;
    font-weight:800;letter-spacing:.1em;
    border:1px solid transparent;border-radius:var(--radius-pill)}
  .spv-call .vpill{font-size:13px;padding:9px 14px}      /* hero */
  .dpill{font-size:11px;padding:7px 12px}                /* decision card */
  .yvp{font-size:10px;padding:5px 9px;margin-right:7px}  /* inline, in prose */
  .ss-v{font-size:10px;padding:4px 8px}                  /* map sheet summary */

  .spv-call.go .vpill,.dpill.go,.ss-v.sv-go,.yvp.go{
    background:var(--accent);color:var(--accent-ink);border-color:var(--accent);
    box-shadow:0 0 14px rgba(232,255,61,.28)}
  .spv-call.maybe .vpill,.dpill.maybe,.ss-v.sv-maybe,.yvp.maybe{
    background:rgba(240,163,78,.16);color:var(--warn);border-color:rgba(240,163,78,.4)}
  .spv-call.nope .vpill,.dpill.nope,.ss-v,.yvp.nope{
    background:rgba(255,255,255,.06);color:var(--text-2);border-color:var(--line)}
  /* PHASE 8.5 REAL-DEVICE CORRECTION: .ss-v.sv-hazard is gone. Hazard used to
     REPLACE the verdict pill's label and colour with "HAZARD" the moment
     s.state was "hazard", hiding the spot's real call. It is now always the
     true verdict pill plus a SEPARATE .dhaz chip -- see spotCallParts() in
     discover-map.js and decisionCard() in app.js -- so this rule has no
     caller left; removed rather than kept unreachable. */

  /* REALITY is deliberately a DIFFERENT shape from a verdict: lower contrast,
     an icon, and never the solid accent fill. A verdict is SPOT's call; reality
     is what people observed. The two must never be mistaken for each other, so
     only one of them is ever allowed to look like a filled pill.

     Phase 8.4 also removed the Discover sheet's private copy of this language
     (.ss-live / .ss-some / .ss-none rendered bare coloured text). The sheet now
     calls realityChip() -- the same function For You and Spot View render -- so
     there is one definition of the wording AND one of the styling. */
  /* PHASE 8.5F FINAL POLISH ROUND 2: .dr was the one pill in this family
     that had NEVER received justify-content:center at all (only align-
     items) -- missed across three separate centering passes because each
     one audited a list of classes named by that pass's own real-device
     report, and .dr's own labels ("NO RECENT REPORTS", "FRESH CLIP") were
     never named until this one. It now inherits justify-content (and
     everything else centering-related) from the one shared contract near
     .chip instead of needing its own copy. */
  .dr{flex:0 0 auto;gap:5px;font-size:10px;font-weight:800;
    letter-spacing:.02em;padding:4px 9px;border-radius:var(--radius-pill);
    border:1px solid transparent}
  .dr i{font-size:11px}
  .dr.live{background:rgba(77,255,200,.12);color:var(--accent-2);border-color:rgba(77,255,200,.26)}
  .dr.some{background:rgba(240,163,78,.12);color:var(--warn);border-color:rgba(240,163,78,.26)}
  .dr.none{background:rgba(255,255,255,.04);color:var(--text-2);border-color:var(--line)}

  /* =====================================================================
     PHASE 8.5 — MOTION / INTERACTION FEEL

     Everything below is the complete, bounded motion set for this product.
     scripts/check-motion-scope.js pins it: motion may live on the selectors
     named here and nowhere else, so it cannot quietly spread into general
     cards, lists or text.
     ===================================================================== */

  /* --- 1. PRIMARY TAB SWAP -------------------------------------------
     Only the CONTENT settles. `.tabswap` suppresses the push transition on
     both screens for one frame-set: the outgoing screen leaves without
     sliding, the incoming one is already in place and its content lifts 6px
     into view. Persistent chrome (status bar, header, bottom nav) is
     untouched by design -- it did not go anywhere. */
  .screen.tabswap{transition:none}
  .screen.tabswap.behind{transform:translateX(100%);opacity:1;visibility:hidden}
  .screen.tabswap.active > .scroll,
  .screen.tabswap.active > .mapwrap,
  .screen.tabswap.active > .listview{animation:contentIn var(--t-screen) var(--ease) both}
  @keyframes contentIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

  /* --- 2. SPOT VIEW ACCORDIONS ---------------------------------------
     grid-template-rows 0fr -> 1fr animates real content height without a
     magic max-height that has to be guessed and re-guessed per spot. The
     panel keeps display:grid in both states, so nothing pops in at the end. */
  .spv-accp{display:grid;grid-template-rows:0fr;
    transition:grid-template-rows var(--t-std) var(--ease),opacity var(--t-std) var(--ease),
               padding var(--t-std) var(--ease),border-color var(--t-std) linear;
    padding-top:0;padding-bottom:0;opacity:0;border-top-color:transparent}
  .spv-accp > .spv-accinner{overflow:hidden;min-height:0}
  .spv-acc.open .spv-accp{grid-template-rows:1fr;opacity:1;padding-bottom:14px;
    border-top-color:var(--line)}
  .spv-chev{transition:transform var(--t-std) var(--ease)}

  /* --- 3. SEGMENTED CONTROL INDICATOR --------------------------------
     One moving pill instead of two independently recolouring buttons, so the
     eye follows the selection rather than re-finding it. Two options only,
     which is why a 50% track is enough -- this is not a generic component. */
  .seg{position:relative;isolation:isolate}
  .seg::before{content:"";position:absolute;z-index:0;top:3px;bottom:3px;left:3px;width:calc(50% - 3px);
    border-radius:var(--radius-pill);background:var(--accent);
    transition:transform var(--t-std) var(--ease)}
  .seg.compact::before{top:2px;bottom:2px;left:2px;width:calc(50% - 2px)}
  .seg[data-sel="1"]::before{transform:translateX(100%)}
  .seg button{position:relative;z-index:1;background:none}
  .seg button.on{background:none;color:var(--accent-ink)}

  /* --- 4. DISCOVER BOTTOM SHEET --------------------------------------
     The sheet is a FIXED-height box that is translated down to reveal only
     the peek. Dragging is therefore pure transform -- no per-frame layout of
     the list inside it -- and the settled peek geometry is exactly the 20%
     Phase 8.4 approved, computed from the container rather than hardcoded to
     one viewport height. --sheet-y is written by discover-map.js. */
  /* NO CSS TRANSITION ON THIS ELEMENT, DELIBERATELY.
     Two attempts failed the same way: a CSSTransition on `transform` -- whether
     fed by a custom property or by a plain inline value -- wedged permanently in
     playState "running" on this element, and a running transition outranks
     inline styles, so the sheet then ignored every position it was given.
     Confirmed with getAnimations(): cancelling that single transition made a
     249px transform apply instantly.
     The settle is therefore driven by the Web Animations API in
     discover-map.js, which commits the final position to the inline style FIRST
     and plays the travel on top without fill. If the animation is dropped for
     any reason the sheet is still in the right place -- state wins over motion.
     `height:66%` with the box translated down to reveal the peek keeps a drag on
     the compositor: no width, no height, no relayout of the list inside it. */
  .mapwrap .sheet{position:absolute;left:0;right:0;bottom:0;margin:0;display:flex;flex-direction:column;
    height:66%;max-height:none;will-change:transform;touch-action:none}
  .sheet .grabrow{touch-action:none;-webkit-user-select:none;user-select:none}
  .sheet .grab{transition:background var(--t-micro) var(--ease),width var(--t-micro) var(--ease)}
  .sheet.dragging .grab{background:var(--text-2);width:46px}
  /* PHASE 8.5 REAL-DEVICE CORRECTION: #mapRows -- the scrollable directory
     this rule carved a pan-y exception for -- is gone; the sheet's content is
     now the compact, non-scrolling quick-context card, so every drag on the
     sheet is a sheet gesture and no such exception is needed. */

  /* --- 5. SELECTED MAP MARKER ----------------------------------------
     The selected pin is rebuilt on every repaint, so this is a one-shot
     entrance on that ONE element. Unselected dots are deliberately excluded:
     a live forecast update repaints all 25 and none of them should move. */
  .spot-pin.sel{animation:pinIn var(--t-std) var(--ease) both}
  @keyframes pinIn{from{opacity:0;transform:translate(-8px,-50%) scale(.9)}
                   to{opacity:1;transform:translate(-8px,-50%) scale(1)}}

  /* --- 6. RECORDING -> COMPOSER --------------------------------------
     Continuity, not a shared-element framework: the viewfinder recedes very
     slightly as it goes, and the attached clip resolves into the compact
     preview so it reads as the same piece of media arriving, not a new
     screen with a new thumbnail. */
  .cam{transition:opacity var(--t-std) var(--e-in),transform var(--t-std) var(--e-in)}
  .cam.handing-off{opacity:0;transform:scale(.965)}
  #s-upload .scroll#confirmView.arriving{animation:contentIn var(--t-std) var(--ease) both}
  #s-upload .scroll#confirmView.arriving .qr-clip{animation:clipLand var(--t-screen) var(--ease) both}
  @keyframes clipLand{from{opacity:0;transform:scale(1.06)}to{opacity:1;transform:none}}

  /* --- 7. QUICK REPORT SELECTION + SUBMIT ----------------------------
     Selection commits instantly; only the FILL interpolates. */
  .qtile{transition:background var(--t-micro) var(--ease),border-color var(--t-micro) var(--ease),
         color var(--t-micro) var(--ease),transform var(--t-micro) var(--ease)}
  .qtile:active{transform:scale(.96)}
  .qr-sticky .cta{transition:opacity var(--t-std) var(--ease),background var(--t-std) var(--ease),
                  color var(--t-std) var(--ease)}
  /* SUBMITTING is a real state, not a spinner: the label says so and the
     control is inert. SUCCESS is shown only after the local commit that the
     existing local-first contract treats as the report existing. */
  .qr-sticky .cta.submitting{pointer-events:none;opacity:.72}
  .qr-sticky .cta.success{background:var(--accent);color:var(--accent-ink);pointer-events:none}

  /* --- 8. BOTTOM NAV PRESS -------------------------------------------
     The bar itself never moves. Only the pressed destination acknowledges. */
  .tab i{transition:transform var(--t-micro) var(--ease)}
  .tab:active i{transform:scale(.9)}

  /* --- 9. REDUCED MOTION ---------------------------------------------
     Functionality is identical; only the spatial storytelling goes. Nothing
     here disables a state change -- panels still open, sheets still move
     between states, screens still swap. They just stop travelling. */
  @media (prefers-reduced-motion: reduce){
    .screen{transition:none}
    .screen.behind{opacity:1}
    .screen.tabswap.active > .scroll,
    .screen.tabswap.active > .mapwrap,
    .screen.tabswap.active > .listview{animation:none}
    .spv-accp{transition:opacity var(--t-micro) linear}
    .spv-acc.open .spv-accp{grid-template-rows:1fr}
    .spv-chev{transition:none}
    .seg::before{transition:none}
    .spot-pin.sel{animation:none}
    .cam{transition:none}
    #s-upload .scroll#confirmView.arriving,
    #s-upload .scroll#confirmView.arriving .qr-clip{animation:none}
    .qtile:active,.tab:active i{transform:none}
    .ddetail{animation:none}
  }
