/* stage.css (visuals): the stage's layers and what's pinned on them: place rings, number cards,
   buoys and place names. A page owns the stage's size and the words over it. */
.stage { position: relative; background: #070d14; overflow: hidden; --vis-sans: var(--sans, -apple-system, system-ui, sans-serif); }
.stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; transition: opacity .18s; }
.stage canvas.fx { mix-blend-mode: screen; }
.stage .tiles { position: absolute; inset: 0; z-index: 0; overflow: hidden; transition: opacity .18s;     /* its own layer: a zoom's tiles stack inside it */
  background: #18212a; filter: brightness(.75); }      /* a tile not in yet is the ground's colour, not black; the satellite a quarter dimmer (critique, 2026-10-03) */
.stage .tiles img { position: absolute; display: block; max-width: none; user-select: none; }
.stage .radar, .stage .clouds { position: absolute; inset: 0; pointer-events: none; overflow: hidden; transition: opacity .18s; }   /* the weather tiles (radarkit.js): clouds over the painted ocean under the lines; rain over the lines, under the wind */
.stage .radar img, .stage .clouds img { position: absolute; display: block; max-width: none; user-select: none; }
.stage .rk { position: absolute; right: 10px; bottom: 20px; z-index: 4; font: 700 10px var(--vis-sans); color: rgba(242,239,228,.85); text-shadow: 0 1px 2px rgba(0,0,0,.7); pointer-events: none; }
.stage .rk[hidden] { display: none; }
.stage.cut .tiles, .stage.cut .radar, .stage.cut .clouds, .stage.cut canvas { opacity: 0; }
.stage.hold canvas.fx, .stage.hold canvas.paint { opacity: .45; }
.stage .credit { position: absolute; right: 10px; bottom: 6px; z-index: 4; font: 10px var(--vis-sans); color: rgba(242,239,228,.78); text-shadow: 0 1px 2px rgba(0,0,0,.7); pointer-events: none; }
.annos { position: absolute; inset: 0; z-index: 2; pointer-events: none; }
.annos svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.annos .lead { stroke: rgba(242,239,228,.55); stroke-width: 1.2; fill: none; }
/* In flight, the last beat's numbers go; the names ride along with the ground. */
.annos .spot, .annos svg, .annos .buoy { transition: opacity .25s; }
.stage.flying .annos .card, .stage.flying .annos .spot, .stage.flying .annos svg, .stage.flying .annos .buoy { opacity: 0; }
.spot { position: absolute; width: 0; height: 0; }
.spot i { position: absolute; left: -7px; top: -7px; width: 14px; height: 14px; border-radius: 50%; border: 2px solid #62d2ff; animation: ping 2.2s ease-out infinite; }
.spot i + i { animation-delay: 1.1s; }
.spot b { position: absolute; left: -4px; top: -4px; width: 8px; height: 8px; border-radius: 50%; background: #62d2ff; box-shadow: 0 0 8px #62d2ff; }
.spot.storm i { border-color: #ffd678; } .spot.storm b { background: #ffd678; box-shadow: 0 0 8px #ffd678; }
@keyframes ping { from { transform: scale(.6); opacity: .95; } to { transform: scale(3.2); opacity: 0; } }
.card { position: absolute; background: rgba(8,14,22,.82); border: 1px solid rgba(242,239,228,.18); border-radius: 10px; padding: 6px 9px 7px;
        color: #f2efe4; font: 12px var(--vis-sans); backdrop-filter: blur(3px); opacity: 0; transform: translateY(4px); transition: opacity .35s, transform .35s; white-space: nowrap; }
.card.in { opacity: 1; transform: none; }
.card.slim { padding: 4px 7px 5px; border-radius: 8px; white-space: normal; } .card.slim .nm { font-size: 9.5px; margin-bottom: 3px; }
.card.slim .ch { font-size: 12px; padding: 1px 6px; margin: 0 3px 3px 0; } .card.slim .bd { font-size: 10px; margin-top: 1px; }
.card .bd { font: 650 11px var(--vis-sans); color: #ffd678; opacity: .85; margin-top: 4px; }
.card .nm { font: 800 10.5px var(--vis-sans); letter-spacing: .09em; text-transform: uppercase; opacity: .85; margin-bottom: 4px; }
.card .ch { display: inline-block; font: 750 13px var(--vis-sans); padding: 2px 7px; border-radius: 6px; margin: 0 4px 0 0; background: rgba(255,255,255,.08); }
.card .wx { display: flex; gap: 5px; align-items: center; margin: 0 0 5px; }      /* a window's weather as the mark's cells (cards.js glyphs): the wave in the wind's colour, the sky, rain */
.annos .card .wx svg { position: static; inset: auto; display: block; width: 18px; height: 18px; pointer-events: none; }     /* not the annos' own full-size svg */
.ch.surf, .ch.standouts { background: rgba(255,194,75,.2); color: #ffd678; } .ch.period { background: rgba(98,210,255,.18); color: #9fe3ff; }
.ch.direction { background: rgba(155,214,107,.18); color: #c3eaa0; } .ch.seas, .ch.storm { background: rgba(255,111,111,.18); color: #ffb0a8; }
.ch.tide { background: rgba(120,160,255,.18); color: #b8ccff; } .ch.wind { background: rgba(255,255,255,.1); color: #ddd; }
.buoy { position: absolute; width: 0; height: 0; }
.buoy i { position: absolute; left: -5px; top: -5px; width: 10px; height: 10px; background: #f2efe4; transform: rotate(45deg); box-shadow: 0 0 6px rgba(255,255,255,.6); }
.buoy.named i { background: #ffd678; box-shadow: 0 0 8px #ffd678; }
.buoy span { position: absolute; left: 9px; top: -8px; font: 700 10.5px var(--vis-sans); color: #f2efe4; background: rgba(8,14,22,.78);
  border: 1px solid rgba(242,239,228,.22); border-radius: 5px; padding: 1px 5px; white-space: nowrap; }
.city { position: absolute; width: 0; height: 0; }
.city i { position: absolute; left: -2.5px; top: -2.5px; width: 5px; height: 5px; border-radius: 50%; background: #f2efe4; opacity: .85; }
.city span { position: absolute; left: 6px; top: -8px; font: 650 11px var(--vis-sans); color: #f2efe4; text-shadow: 0 1px 3px #000, 0 0 2px #000; white-space: nowrap; opacity: .92; }
.city.big span { font-size: 12px; font-weight: 750; }
/* The fine print's quotes: the forecasters' words, centred, the one being said bright, those before it dimmed (their words, not their card, so nothing shows through). */
.quotes { position: absolute; z-index: 1; display: flex; flex-direction: column; gap: 10px; }
.quote { margin: 0; padding: 12px 14px 10px; border-radius: 10px; background: rgba(8,14,22,.86); border: 1px solid rgba(242,239,228,.16);
         border-left: 3px solid #ffd678; backdrop-filter: blur(4px); color: #f2efe4; opacity: 0; transform: translateY(6px); transition: opacity .45s, transform .45s; }
.quote.in { opacity: 1; transform: none; } .quote.in:not(.now) > * { opacity: .5; } .quote > * { transition: opacity .45s; } .quote.gone { opacity: 0; }
.quotes.one .quote { position: absolute; left: 0; right: 0; }
/* A quote that opens its bulletin (cards.js onQuote; Kurtis, 2026-10-09: "all those cards need to beg to be clicked to see the
   full story … the long-form official bulletin"): a gold edge, a gold button across the foot of the card, a lift on hover. */
.quote.link { pointer-events: auto; cursor: pointer; border-color: rgba(255,214,120,.5); transition: border-color .2s, box-shadow .2s, opacity .45s, transform .45s; }
.quote.link:hover, .quote.link:focus-visible { border-color: #ffd678; box-shadow: 0 0 0 1px #ffd678, 0 8px 24px rgba(0,0,0,.45); outline: none; }
.quote.link figcaption { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; }
.quote.link figcaption .go { margin-left: auto; padding: 8px 14px; border-radius: 999px; background: #ffd678; color: #0b1219; font: 800 13px/1 var(--vis-sans); white-space: nowrap; }
.quote.link:hover figcaption .go { background: #ffe39b; }
.quote.in:not(.now) > figcaption .go { opacity: 1; }
.quote blockquote { margin: 0; font: 550 clamp(14.5px, 2vmin, 24px)/1.42 var(--vis-sans); }
.quote blockquote::before { content: '“'; } .quote blockquote::after { content: '”'; }
.quote .ldr { display: inline-block; width: 1.4em; margin: 0 .35em; border-bottom: 2px dotted rgba(242,239,228,.5); vertical-align: .28em; }
.quote figcaption { margin-top: 7px; font: 750 clamp(9.5px, 1.15vmin, 14px) var(--vis-sans); letter-spacing: .08em; text-transform: uppercase; color: #ffd678; opacity: .85; }
.stage.flying .annos .quotes { opacity: 0; }
/* The key to the swell lines (key.js): a row per swell drawn, its swatch the line's colour and thickness, then what they mean.
   Sized in em, so a page sets one font size (the TV's is in vmin); it wraps to the room it has. */
.swkey { display: flex; flex-wrap: wrap; align-items: center; gap: .25em 1.1em; font: 600 12px/1.35 var(--vis-sans, -apple-system, system-ui, sans-serif); color: #f2efe4; }
.swkey:empty { display: none; }
.swkey .row { display: inline-flex; align-items: center; gap: .5em; min-width: 0; }
.swkey .row b { font-weight: 780; white-space: nowrap; } .swkey .row > span { white-space: nowrap; opacity: .92; }
.swkey .row.dim { opacity: .62; }
.swkey .swk-line { flex: none; display: inline-block; width: 1.7em; border-radius: 99px; }
.swkey .swk-ring { flex: none; display: inline-block; width: 1.15em; height: .6em; border-style: solid; border-bottom: 0; border-radius: 1.15em 1.15em 0 0; box-sizing: border-box; }
.swkey .foot { font-weight: 550; opacity: .7; white-space: nowrap; }
.swkey .far { flex-basis: 100%; margin-top: -.15em; font-weight: 600; opacity: .9; }     /* a storm's swell where we watch: a line of its own, wrapping */
.swkey.tv { font-size: 2.1vmin; margin-top: 1.4vmin; max-width: 70vw; translate: var(--sx, 0) var(--sy, 0); }
/* On a lab page's map: bottom left, over the water (a page moves it clear of its own captions). */
.swkey.lab { position: absolute; left: 12px; bottom: 10px; max-width: calc(100% - 24px); box-sizing: border-box; z-index: 3; pointer-events: none;
  padding: 6px 10px; border-radius: 9px; background: rgba(8,14,22,.72); backdrop-filter: blur(3px); }
/* A named swell's route (route.js): its birth, its line home's labels, where the model lost it, the key and the run's clock. */
.stage.rt .sizer { width: 100%; aspect-ratio: 16 / 9; } @media (max-width: 640px) { .stage.rt .sizer { aspect-ratio: 4 / 5; } }
.stage.rt { cursor: pointer; } .stage.rt.held canvas.fx { opacity: .7; } .stage.rt .tiles { filter: brightness(.55) saturate(.85); }
.rt-l { position: absolute; transform: translate(12px, -50%); font: 750 12.5px/1.15 var(--vis-sans); color: #f2efe4; white-space: nowrap;
  text-shadow: 0 1px 3px #000, 0 0 2px #000; } .rt-l.l { transform: translate(calc(-100% - 12px), -50%); text-align: right; }
.rt-l.faint { color: #ffb0a8; font-size: 11.5px; } .rt-l small { display: block; font: 600 10.5px var(--vis-sans); color: rgba(242,239,228,.78); }
.rt-p { position: absolute; width: 30px; height: 30px; margin: -15px 0 0 -15px; }      /* a pin's ring, kept clear of place names */
.rt-b { position: absolute; width: 12px; height: 12px; margin: -6px 0 0 -6px; border-radius: 50%; background: #ffd678; box-shadow: 0 0 0 3px rgba(255,214,120,.3), 0 0 10px #ffd678; }
.rt-x { position: absolute; width: 14px; height: 14px; margin: -7px 0 0 -7px; border: 2px solid rgba(242,239,228,.85); border-radius: 50%; }
.rt-x::after { content: ''; position: absolute; left: 50%; top: -3px; bottom: -3px; width: 2px; margin-left: -1px; background: rgba(242,239,228,.85); transform: rotate(45deg); }
.rt-k { position: absolute; left: 10px; bottom: 8px; z-index: 4; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; max-width: calc(100% - 20px);
  font: 600 11.5px var(--vis-sans); color: #f2efe4; background: rgba(5,9,13,.62); border-radius: 8px; padding: 4px 8px; pointer-events: none; }
.rt-k i { display: inline-block; width: 16px; height: 4px; border-radius: 2px; } .rt-k i.d { height: 0; border-top: 2px dashed #ffd678; border-radius: 0; margin-left: 4px; }
.rt-clock { position: absolute; right: 10px; top: 8px; z-index: 4; font: 750 12.5px var(--vis-sans); color: #f2efe4; text-shadow: 0 1px 3px #000; pointer-events: none; }
.stage.rt .credit { bottom: auto; top: 28px; right: 10px; }

/* The panels over the words' band, left (.tg): the chart (.tg.tl, stage.js trackGantt: the Swell tracker's Gantt under the tracker shot; off
   on ordinary beats since 2026-10-17, config/story.json chart), the windows panel (.tg.wp) and the advisory panel (.tg.ag); gone in flight. */
.tg { position: absolute; left: 16px; right: 16px; max-width: 620px; z-index: 3; pointer-events: none; padding: 8px 10px 4px;
      border-radius: 12px; transition: opacity .3s;
      /* softer (Kurtis, 2026-10-11: "the dark background, can we soften it a bit"): a light veil that blurs the map behind it, darker only at
         the foot where the day names sit, with a hairline edge so it still reads as a panel */
      background: linear-gradient(rgba(8,14,22,.18), rgba(8,14,22,.34)); backdrop-filter: blur(8px) saturate(1.1); -webkit-backdrop-filter: blur(8px) saturate(1.1);
      border: 1px solid rgba(242,239,228,.12); }
.tg svg { display: block; width: 100%; height: auto; overflow: visible; }
/* the windows panel (stage.js windowsPanel): dawn · mid-morning · afternoon across the region */
.tg.wp { padding: 8px 10px 10px; }
.tg.wp .wd { font: 800 11px/1 var(--vis-sans); letter-spacing: .12em; text-transform: uppercase; color: rgba(242,239,228,.7); margin: 0 2px 7px; }
.tg.wp .wc { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
.tg.wp .cell { background: rgba(5,9,13,.55); border: 1px solid rgba(242,239,228,.14); border-radius: 10px; padding: 8px 10px; min-width: 0; }
.tg.wp .cell b { display: block; font: 800 10.5px/1 var(--vis-sans); letter-spacing: .1em; text-transform: uppercase; color: rgba(242,239,228,.62); margin-bottom: 5px; white-space: nowrap; }
.tg.wp .sf { display: block; font: 800 clamp(18px, 2.2vw, 26px)/1.05 var(--vis-sans); letter-spacing: -.01em; color: #f2efe4; margin-bottom: 4px; }
.tg.wp .wn, .tg.wp .td, .tg.wp .rn { display: block; font: 600 12.5px/1.3 var(--vis-sans); color: rgba(242,239,228,.78); }
.tg.wp .rn { color: #9cc7f0; }      /* the window's rain, from the NWS hourly forecast (nearshore.rain) */
.tg.wp .wn i { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 5px; vertical-align: middle; background: #9aa; }
.tg.wp .t-go .wn i { background: #6cc38c; } .tg.wp .t-fair .wn i { background: #f0c94a; } .tg.wp .t-poor .wn i { background: #ec7d74; }
.tg.wp .t-go .wn { color: #9fe0b4; } .tg.wp .t-fair .wn { color: #f3d97a; } .tg.wp .t-poor .wn { color: #f3a39b; }
.tg .tc { min-height: 1em; font: 800 clamp(20px, 2.6vw, 32px)/1 var(--vis-sans); letter-spacing: -.015em; color: #f2efe4; margin: 2px 2px 8px;
          text-shadow: 0 1px 3px rgba(0,0,0,.6); font-variant-numeric: tabular-nums; }
.tg .ln { fill: none; stroke: rgba(242,239,228,.92); stroke-width: 3.6; stroke-linejoin: round; stroke-linecap: round; }
.tg .ar { fill: rgba(242,239,228,.12); } .tg .ar.far { fill: rgba(242,239,228,.05); }
.tg .ln.far { stroke: rgba(242,239,228,.5); stroke-dasharray: 4 4; }      /* past the surf table: the model's outlook */
.tg .dl { stroke: rgba(242,239,228,.16); stroke-width: 1; }
.tg .sh { fill: rgba(8,14,22,.24); }
.tg text { paint-order: stroke; stroke: rgba(5,9,13,.55); stroke-width: 2.5px; stroke-linejoin: round; }      /* the day names hold up over a bright sea */
.tg .ln { filter: drop-shadow(0 1px 2px rgba(0,0,0,.55)); }

.tg text { font: 700 10px var(--vis-sans); fill: rgba(242,239,228,.62); letter-spacing: .04em; }
.tg .ph line { stroke: #ffd678; stroke-width: 1.6; } .tg .ph circle { fill: #ffd678; stroke: #0b1219; stroke-width: 1.5; }
/* the timeline stays through a flight: it's the story's, not the beat's (its window glides to the next beat's on landing) */
.tg .win { fill: rgba(255,214,120,.07); stroke: #ffd678; stroke-width: 1.4; animation: tl-breathe 2.6s ease-in-out infinite; }
@keyframes tl-breathe { 0%, 100% { stroke-opacity: .45; filter: drop-shadow(0 0 1px rgba(255,214,120,.4)); } 50% { stroke-opacity: 1; filter: drop-shadow(0 0 6px rgba(255,214,120,.9)); } }
/* one left edge for everything on the story (Kurtis, 2026-10-11: "the bottom blocks aligning … not flush aligned on the left edge"): the
   panel, the words under it (appkit.js .ak-story .low: 16 px, 20 px from a tablet up) and the fine print's quotes share the gutter */
@media (min-width: 640px) { .tg { left: 20px; right: 20px; } }
@media (prefers-reduced-motion: reduce) { .tg .win { animation: none; stroke-opacity: .9; } }

/* The fine print's advisory map (stage.js alertPanel): the hazards on a calendar, a bar each, named with its times; the surf a
   hazard gives, by its coast. No clock. */
.tg.ag .ah { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin: 0 2px 8px; color: #f2efe4; }
.tg.ag .ah b { font: 800 clamp(17px, 1.9vw, 24px)/1 var(--vis-sans); letter-spacing: -.01em; }
.tg.ag .ah span { font: 600 clamp(12px, 1.05vw, 14px) var(--vis-sans); color: rgba(242,239,228,.7); }
.tg.ag .ah .k { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; }
.tg.ag .ah .hk { width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 6px solid rgba(242,239,228,.85); }
.tg.ag .bt { font: 750 12px var(--vis-sans); fill: #0b1219; } .tg.ag .dy { font: 700 11px var(--vis-sans); }
@media (min-width: 900px) { .tg.ag .bt { font-size: 14.5px; } .tg.ag .dy { font-size: 13px; } .tg.ag { max-width: 720px; } } .tg.ag .bt.out { fill: rgba(242,239,228,.92); }
.tg.ag .bt:not(.dk):not(.out) { fill: #f7f5ee; }
.tg.ag .bt.ov { paint-order: stroke; stroke: rgba(8,14,22,.85); stroke-width: 3px; stroke-linejoin: round; }
.tg.ag .nw { stroke: #ffd678; stroke-width: 2; }
.tg.ag .hi { fill: rgba(242,239,228,.9); }
.ac { position: absolute; z-index: 2; transform: translate(16px, -50%); pointer-events: none; opacity: 0; transition: opacity .4s;
      padding: 4px 8px; border-radius: 8px; background: rgba(8,14,22,.78); border-left: 3px solid var(--c); white-space: nowrap; }
.ac.l { transform: translate(calc(-100% - 16px), -50%); border-left: 0; border-right: 3px solid var(--c); text-align: right; }
.ac.on { opacity: 1; } .ac b { display: block; font: 800 13px var(--vis-sans); color: #f2efe4; } .ac span { font: 600 10px var(--vis-sans); color: rgba(242,239,228,.7); }
.stage.flying .ac { opacity: 0; }
/* The beat's clock on the map (stage.js clockChip): the day and hour beside the showcased place, which way the surf goes under it. */
.ck { position: absolute; z-index: 2; transform: translate(18px, -50%); pointer-events: none; opacity: 0; transition: opacity .4s;
      padding: 5px 9px 6px; border-radius: 9px; background: rgba(8,14,22,.8); border: 1px solid rgba(242,239,228,.18); border-left: 3px solid #ffd678;
      backdrop-filter: blur(3px); white-space: nowrap; font-variant-numeric: tabular-nums; }
.ck.l { transform: translate(calc(-100% - 18px), -50%); border-left-width: 1px; border-right: 3px solid #ffd678; text-align: right; }
.ck.d { transform: translate(-50%, 16px); text-align: center; } .ck.u { transform: translate(-50%, calc(-100% - 16px)); text-align: center; }
.ck.on { opacity: 1; }
.ck b { display: block; font: 800 clamp(15px, 1.6vw, 20px)/1.1 var(--vis-sans); color: #f2efe4; letter-spacing: -.01em; }
.ck .tr { display: block; margin-top: 2px; font: 650 11px var(--vis-sans); color: rgba(242,239,228,.78); } .ck .tr[hidden] { display: none; }
.ck .tr::before { margin-right: 4px; font-weight: 800; } .ck .tr.up::before { content: '↗'; color: #9fe0b4; } .ck .tr.flat::before { content: '→'; color: #f3d97a; } .ck .tr.down::before { content: '↘'; color: #f3a39b; }
.stage.flying .ck, .stage.hold .ck { opacity: 0; }
@media (max-width: 639px) { .ck { padding: 4px 7px 5px; } .ck b { font-size: 14px; } .ck .tr { font-size: 10px; } }      /* a phone's stage is narrow */

/* the timeline's rows for swells not on the map this beat */
.tg rect.dim { opacity: .28; transition: opacity .5s; }

/* the timeline opens the Swell tracker on story pages (player.js: .tg-link): a quiet way in, top right, brighter on hover */
.tg .tgo { display: none; position: absolute; top: 10px; right: 12px; font: 750 11.5px var(--vis-sans); letter-spacing: .02em; color: #ffd678;
           border: 1px solid rgba(255,214,120,.45); border-radius: 999px; padding: 3px 9px; background: rgba(8,14,22,.4); }
.tg-link .tg:not(.ag) .tgo { display: block; }
.tg-hot, .tg-hot * { cursor: pointer !important; }
.tg-hot .tg-link .tg:not(.ag) { border-color: rgba(255,214,120,.5); }
.tg-hot .tg-link .tg:not(.ag) .tgo { background: rgba(255,214,120,.18); }
