/* Cybernuke — a saturated golden field under heavy black type, with the launch
   command shown in a dark terminal card. Display and body text use Archivo; the
   terminal bits use JetBrains Mono. Both fonts are open and self-hosted here —
   no CDN, no webfont hotlinking. */

@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/archivo-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('/fonts/archivo-800.woff2') format('woff2');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-400.woff2') format('woff2');
}

:root {
  --gold:      #ffd208;   /* page background + accent (STATIC default — every
                             report page inherits this; only body.home rebinds it) */
  --cream:     #fff7ec;
  --ink:       #111314;   /* black type */
  --ink-mute:  rgba(17, 19, 20, 0.72);   /* muted ink — kept ≥ AA on the gold field */
  --term-bg:   #0a0b0c;   /* dark code card */
  --term-line: #23262a;
  --term-grey: #7f8791;
  --sans: 'Archivo', system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* Brightness dial (home page only). --d is the single knob: 0 = full daylight
     gold, 1 = night. It is a plain inherited number so the head script can stamp
     it on <html> before first paint (no flash), and body.home derives the whole
     field/type palette from it below. Everything NOT scoped to body.home ignores
     --d entirely, so the findings/news corpus keeps its fixed gold theme. */
  --d: 0;
  /* Accent gold that must STAY gold regardless of the dial: the "$" prompt on the
     dark terminal card, the selected agent tab. (--gold itself becomes the page
     field on body.home, so it can no longer double as this fixed accent.) */
  --accent: #ffd208;
}

/* --- brightness dial: day → night interpolation (SITE-WIDE) ------------
   Applies to every page that loads this stylesheet (home, findings, news), so a
   darkness set on one page survives navigation to the next — each page's own
   copy of /dial.js stamps --d from localStorage before first paint, and the
   palette below re-derives from it. The dial keeps black-on-gold for the first
   stretch (just dimming the bright field), then flips to a charcoal field with
   warm amber type over a narrow high band, so most of the travel sits in clearly-
   readable territory and only a short mid-band is "in transition". oklab keeps the
   ramp even. At --d:0 the tokens resolve to the exact static day values, so a page
   with no script (or an old browser without color-mix) renders the original gold. */
body {
  --amber: #bd8b12;   /* dusk floor: dimmed gold, still ≥ AA under black type */
  --night: #0f1114;   /* night field: a hair above the #0a0b0c card, so the card insets */
  --lum:   #f1e3b2;   /* night type: warm amber-cream, high contrast on --night */

  /* Two clamped percentage ramps, inlined directly as color-mix weights:
       day-dim  = clamp(0%, d/0.6·100%, 100%)      — gold → amber over d∈[0,0.60]
       night    = clamp(0%, (d−0.66)/0.24·100%)    — → charcoal over d∈[0.66,0.90]
     The gap between is a readable "dusk" plateau (amber field, black type), and
     the night flip is kept narrow so the unavoidable equal-luminance crossover
     (field ≈ type) is a thin sliver you drag through, not a place you land. They
     are inlined (not held in intermediate custom props) on purpose: a unitless
     clamp() stored in a custom property and then multiplied by 100% across the
     var() boundary silently collapses to 0% in the engine, freezing the palette.
     Kept in percentage units here, it interpolates correctly. oklab = even ramp. */
  --gold: color-mix(in oklab,
            color-mix(in oklab, #ffd208,
              var(--amber) clamp(0%, calc(var(--d) / 0.6 * 100%), 100%)),
            var(--night) clamp(0%, calc((var(--d) - 0.66) / 0.24 * 100%), 100%));
  --ink:  color-mix(in oklab, #111314,
            var(--lum) clamp(0%, calc((var(--d) - 0.66) / 0.24 * 100%), 100%));
  /* muted type = the current type at 72% alpha. At day --ink is #111314, so this
     is EXACTLY the original rgba(17,19,20,0.72) — the findings corpus is unchanged
     at d=0 — and it lifts with --ink at night. srgb so the alpha reads literally. */
  --ink-mute: color-mix(in srgb, var(--ink) 72%, transparent);

  /* ease programmatic jumps; during a drag the input fires continuously so each
     step is tiny and this reads as a smooth follow, not a lag. */
  transition: background-color .12s linear, color .12s linear;
}

* { box-sizing: border-box; }

/* keyboard focus — a solid ink ring with a little breathing room, clearly
   visible against the gold field. On the dark terminal card the ink ring would
   vanish, so those controls switch to a gold ring instead. */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: 2px;
}
.win-body button:focus-visible,
.model:focus-visible {
  outline-color: var(--accent);
}

html { height: 100%; }

body {
  margin: 0;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--gold);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
}

/* one content column shared by everything (hero, terminal, story, manifesto)
   so left and right edges line up at every width — 688 - 2*24 padding = 640. */
main {
  flex: 1;
  width: 100%;
  max-width: 688px;
  margin: 0 auto;
  padding: 14vh 24px 0;
}

h1 {
  margin: 0 0 26px;
  font-family: var(--sans);
  font-weight: 800;
  font-size: clamp(48px, 9.5vw, 92px);
  letter-spacing: -0.02em;
  line-height: 0.98;
  color: var(--ink);
}

/* --- typed tagline: line 1 stays, line 2 runs one pass then settles --- */

.term {
  margin: 0 0 40px;
  font-family: var(--mono);
  font-size: clamp(15px, 2.3vw, 19px);
  line-height: 1.5;
  color: var(--ink);
  min-height: 3em;   /* hold two lines: no reflow while typing */
}

/* line 1 can wrap to two lines on narrow viewports; pre-wrap keeps the monospace
   spacing while letting long lines fold, so no horizontal scroll at 320px. */
.term .row { display: block; min-height: 1.5em; white-space: pre-wrap; overflow-wrap: break-word; }

/* Progressive enhancement: with JS the static fallback is hidden and the (empty)
   rows animate; without JS the rows are hidden and the fallback carries the text
   so there's no blank gap under the announced aria-label. */
.js .term .fallback { display: none; }
:root:not(.js) .term .row { display: none; }
.term .fallback { white-space: pre-wrap; overflow-wrap: break-word; }

/* Each row = a visible "typed" span + a "ghost" holding the untyped remainder.
   The ghost reserves the final layout, so wrap points never shift while typing —
   a word can't grow at a line end and then jump (or split) onto the next line;
   it reveals in place, whole, exactly where it finally sits. `visibility: hidden`
   (not `color: transparent`) keeps that reserved space while staying unpainted,
   unselectable, and invisible to find-in-page. */
.term .row .g { visibility: hidden; }

/* one shell prompt on line 1; line 2 is indented by the prompt's width so its
   first letter sits directly under line 1's first letter, past the "$ ". */
#row1::before { content: "$ "; color: var(--ink-mute); }
#row2 { padding-left: 2ch; }

/* Cursor rides the boundary between typed text and the ghost. It is zero-advance
   (inline-block, width 0) so it overlays the hidden ghost rather than widening the
   line: the typing-time wrap then equals the final wrap exactly — without this the
   cursor's ~1ch box can push a word to wrap a cell early, then snap back. */
.term .row.typing .t::after {
  content: '▌';
  display: inline-block;
  width: 0;
  animation: blink 1.06s step-end infinite;
}

@keyframes blink { 50% { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .term .row.typing .t::after { animation: none; }
}

/* --- description --------------------------------------------------- */

.desc {
  margin: 0 0 40px;
  font-size: clamp(16px, 2.2vw, 19px);
  line-height: 1.6;
  color: var(--ink);
  text-align: justify;
}

/* --- single command to launch -------------------------------------- */

/* dark terminal card on the gold page */
.win {
  max-width: 640px;
  background: var(--term-bg);
  border: 1px solid var(--ink);
  border-radius: 8px;
  overflow: hidden;
}

.win-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--term-line);
}

.dots { display: flex; gap: 7px; }
.dots i {
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--term-line);
}

/* model selector, top-right */
.models { display: flex; gap: 4px; }
.model {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--term-grey);
  background: transparent;
  border: 0;
  border-radius: 4px;
  padding: 4px 9px;
  cursor: pointer;
}
.model:hover { color: var(--cream); }
/* selected tab stays a gold chip on the dark card at every dial position, so its
   label is pinned to fixed dark ink (var(--ink) lifts to light at night). */
.model.is-on { color: #111314; background: var(--accent); }

.win-body {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  padding: 18px 20px;
}

.win-body code {
  flex: 1;
  min-width: 0;              /* let the code shrink so it wraps, never scrolls */
  min-height: 4.8em;         /* reserve 3 lines so the card height never jumps
                                between a 2-line and a 3-line command */
  font-family: var(--mono);
  font-size: clamp(12.5px, 1.9vw, 15px);
  line-height: 1.6;
  color: var(--cream);
  white-space: pre-wrap;     /* honour the "\" newlines AND wrap long lines */
  overflow-wrap: break-word; /* wrap at spaces first, break a token only if it
                                can't fit — no mid-URL snapping, no H-scroll */
}

.win-body code::before { content: "$ "; color: var(--accent); }

/* Copy: bare icon, no box. Muted by default, lifts on hover; flashes green for
   a couple of seconds on click (no icon swap), then back. currentColor drives
   the SVG stroke. */
.win-body button {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  color: rgba(255, 255, 255, 0.4);
  background: none;
  border: 0;
  cursor: pointer;
  transition: color .15s ease;
}
.win-body button:hover { color: rgba(255, 255, 255, 0.6); }
.win-body button.ok { color: #6fd39b; }
.win-body button svg { width: 18px; height: 18px; display: block; }
/* show the copy icon by default, the check only while .copied */
.win-body button .ic-done { display: none; }
.win-body button.copied .ic-copy { display: none; }
.win-body button.copied .ic-done { display: block; }

.launch-note {
  margin: 14px 0 0;
  max-width: 640px;   /* match the terminal card, so it sits under it */
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-mute);
  text-align: justify;
}

.launch-note a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in oklab, var(--ink), transparent 70%);
}
.launch-note a:hover { border-bottom-color: var(--ink); }

/* the story continues after the terminal — same body copy as the desc above:
   what cybernuke is (before the terminal) -> what it's achieved (here). */
.story {
  margin: 28px 0 0;
  font-size: clamp(16px, 2.2vw, 19px);
  line-height: 1.6;
  color: var(--ink);
  text-align: justify;
}

.story a, .desc a {
  color: var(--ink);
  font-weight: 800;
  text-decoration: none;
  border-bottom: 2px solid var(--ink);
  white-space: nowrap;
}

/* The marks band is sized in vw, which counts the scrollbar's width, so it
   overhangs the client area by a few px and offers a sideways scroll. Scoped to
   the landing page and NOT set on `body`: this stylesheet is shared with every
   report, where a wide table has no scroll wrapper of its own and hiding the
   document's x-overflow would clip it rather than let a reader reach it. */
body.home { overflow-x: hidden; }

/* --- who we've shipped findings to ---------------------------------
   A slow ticker of the projects the corpus has reported into. It breaks the
   688px column deliberately, the way the manifesto does: the page has been one
   narrow measure up to here, so running the marks edge to edge reads as a
   different kind of statement rather than another paragraph.

   WHY WORDMARKS AND NOT LOGOS. Checked 2026-08-13 against Wikipedia and
   Commons: of the projects named here only PuTTY publishes a logo under a
   licence we could reuse (File:PuTTY Icon upstream.svg, MIT). Botan, wolfSSL,
   NSS, mbedTLS, lwIP and MirageOS have no logo file on Wikipedia at all, and
   libssh and picoTCP have no article. Wikipedia's other logos are non-free —
   hosted locally under fair use for an encyclopedia, which does not extend to
   us. So a "logo ticker" would be one MIT icon beside six gaps. Setting the
   marks in Archivo instead is licence-clean, self-hosted like the fonts, holds
   the design's one-typeface discipline on the gold field, and — the part that
   matters most — carries no implication that any of these projects endorse
   cybernuke. Trademark law allows naming a project to refer to it; borrowing
   its logo is where the endorsement reading starts. */

/* Full-bleed from inside the 688px column: main is centred, so pulling each
   side out by (50vw - 50%) lands the band on the viewport edges. It stays a
   sibling of the story rather than a block after </main> — main is flex:1, so
   anything past it gets shoved to the bottom of a tall viewport and the marks
   detach from the sentence they evidence. */
.marks {
  width: 100vw;
  margin: 46px 0 0;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding-bottom: 26px;   /* clear air before the manifesto's own top padding */
  overflow: hidden;       /* the rail is wider than the page; never scroll the body */
}

.marks-lab {
  max-width: 688px;
  margin: 0 auto 16px;
  padding: 0 24px;
  font-family: var(--mono);
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--ink-mute);
}

/* Fade both ends into the gold field so a mark enters and leaves rather than
   being guillotined at the viewport edge. mask-image is the only part of this
   section that degrades: where it is unsupported the marks simply hard-clip,
   which is tidy enough. */
.marks-rail {
  display: flex;
  width: 100%;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 70px, #000 calc(100% - 70px), transparent);
          mask-image: linear-gradient(90deg, transparent, #000 70px, #000 calc(100% - 70px), transparent);
}

/* Three identical tracks side by side, each translated left by exactly its own
   width: track 2 lands where track 1 began, so the loop has no seam. Three and
   not two because two only covers a viewport up to one track wide (~1200px
   here) — beyond that a gap opens at the right as the pair slides off. */
.marks-track {
  display: flex;
  flex: none;
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
  animation: marks-roll 30s linear infinite;
  will-change: transform;
}

/* The gap rides on the item, not the track, so the spacing across a track
   boundary equals the spacing inside one. A `gap` would drop the last one. */
.marks-track li { flex: none; margin-right: 52px; }

.marks-track a {
  display: block;
  font-family: var(--sans);
  font-weight: 800;
  font-size: clamp(24px, 4.6vw, 36px);
  letter-spacing: -0.015em;
  line-height: 1.15;
  white-space: nowrap;
  color: var(--ink-mute);   /* muted like a logo cloud, but still AA on gold —
                               these are links carrying the claim, not texture */
  text-decoration: none;
  transition: color .18s ease;
}
.marks-track a:hover,
.marks-track a:focus-visible { color: var(--ink); }

/* Hold the ticker still while it is being read or tabbed through — a moving
   link is a target you have to chase. */
.marks-rail:hover .marks-track,
.marks-rail:focus-within .marks-track { animation-play-state: paused; }

@keyframes marks-roll {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-100%, 0, 0); }
}

/* Reduced motion: no scroll at all. One track, wrapped into the same column
   the rest of the page uses, so the list is simply read rather than watched. */
@media (prefers-reduced-motion: reduce) {
  .marks-rail {
    display: block;
    max-width: 688px;
    margin: 0 auto;
    padding: 0 24px;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .marks-track { animation: none; flex-wrap: wrap; row-gap: 4px; }
  .marks-track[aria-hidden="true"] { display: none; }
}

/* --- buried manifesto ----------------------------------------------
   Real, selectable, crawlable text set barely off the gold field — it reads
   as background until you select it, but it's there for anyone who looks. */

footer { background: var(--gold); }

.manifesto {
  max-width: 1200px;             /* wide easter-egg layer: overflow the content
                                    margins and use that space so it stays short
                                    — only goes tall once the viewport narrows */
  margin: 0 auto;
  padding: 28px 24px 48px;
  font-family: var(--sans);
  font-size: 22px;
  font-weight: 800;
  line-height: 1.05;
  text-align: justify;
  /* one step off the field: a ghost until you select it. Tracks the dial so it
     stays a ghost on charcoal at night, not a suddenly-bright gold block. */
  color: color-mix(in oklab, var(--gold) 92%, var(--ink));
}

.manifesto::selection { background: var(--ink); color: var(--gold); }

/* --- responsive: keep narrow columns readable ---------------------- */

/* justified text opens into rivers on a narrow column; hyphenate (keeping the
   justify) so lines fill and the gaps close. */
@media (max-width: 560px) {
  .desc, .story, .manifesto, .launch-note { -webkit-hyphens: auto; hyphens: auto; }

  /* The two-row tagline breaks once line 1 itself wraps (48 mono chars need
     ~430px): the "$" prompt / 2ch-indent alignment falls apart. Collapse to
     one flowing line — "…cryptosystems: detect · exploit · patch" — that wraps
     like prose, joined by the ": " separator pinned in via sep1/sep2 (below).
     Reserve the tallest step: the joined line for "exploit, demonstrate
     vulnerability on the wire" wraps to four lines at ≤360px (three elsewhere),
     so hold four here — otherwise the block below shifts when that step enters
     and leaves. Above ~365px every step fits in three lines and the fourth is
     just slack the anchored copy below never notices. */
  .term { min-height: 6em; }
  .term .row { display: inline; }
  #row2 { padding-left: 0; }
  /* The separator is revealed one glyph at a time (":" then ": ") so the cursor
     advances a single cell per step — like a keystroke — instead of leaping two
     cells over a separator that pops in all at once. It is not part of row2's
     text, so the step erase/retype loop never touches it, and it holds once set. */
  .term.sep1 #row2::before { content: ':'; }
  .term.sep2 #row2::before { content: ': '; }
}

@media (max-width: 400px) {
  .model { padding: 4px 6px; }        /* four agent tabs fit the terminal bar */
}

/* --- brightness dial control (site-wide, injected by /dial.js) --------
   "A line you pull left or right": a slim track with a small handle, sun at the
   day end, moon at the night end. Sits top-right of the page and scrolls away with it. Colours come from the same
   --ink/--ink-mute tokens the dial itself drives, so the control re-tints as the
   page darkens. Present on every page, so the darkness can be adjusted anywhere,
   not only on the home page. */
.dial {
  position: absolute; top: 1.05rem; right: 1.25rem; z-index: 20;
  display: flex; align-items: center; gap: 8px;
}
.dial-i {
  font-size: .8rem; line-height: 1;
  color: var(--ink-mute);
  -webkit-user-select: none; user-select: none;
}

/* Reset the native control, then paint a 2px track + 12px round thumb in both
   the WebKit and Gecko pseudo-elements. The negative thumb margin centres it on
   the thin track (WebKit only; Gecko centres automatically). */
.dial-range {
  -webkit-appearance: none; appearance: none;
  width: 96px; height: 18px; margin: 0;
  background: transparent; cursor: pointer;
}
.dial-range::-webkit-slider-runnable-track {
  height: 2px; border-radius: 2px; background: var(--ink-mute);
}
.dial-range::-moz-range-track {
  height: 2px; border-radius: 2px; background: var(--ink-mute);
}
.dial-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 12px; height: 12px; margin-top: -5px;
  border: 0; border-radius: 50%; background: var(--ink);
}
.dial-range::-moz-range-thumb {
  width: 12px; height: 12px;
  border: 0; border-radius: 50%; background: var(--ink);
}
.dial-range:focus { outline: none; }
.dial-range:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 4px; border-radius: 8px;
}

@media (max-width: 520px) {
  .dial { top: .7rem; right: .8rem; gap: 6px; }
  .dial-range { width: 72px; }
}

/* never print the control */
@media print { .dial { display: none !important; } }

/* Related-findings / news gallery cards carry OG thumbnails on a white matte
   (set in the report/press inline <style>). At night, swap the matte for a dark
   inset frame so a strip of bright white doesn't punch through the charcoal
   field. `body .report .alsolike-card` (0,2,1) outranks the inline
   `.report .alsolike-card` (0,2,0) from the shared sheet, so no per-page rebuild
   is needed; at --d:0 it resolves to #fff, unchanged. Covers findings and news
   (press-template reuses .report). */
body .report .alsolike-card {
  background: color-mix(in srgb, #fff,
                #14161a clamp(0%, calc((var(--d) - 0.66) / 0.24 * 100%), 100%));
}

/* top-right site nav on the landing page (findings · news) */
.home .topnav { position: absolute; top: 1.05rem; right: 1.25rem; z-index: 10;
  display: flex; gap: 1.05rem;
  font: 600 .95rem/1 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
  letter-spacing: .02em; }
.home .topnav a { color: var(--ink-mute); text-decoration: none; }
.home .topnav a:hover { color: var(--ink); }
@media (max-width: 520px) { .home .topnav { top: .7rem; right: .8rem; gap: .8rem; font-size: .84rem; } }
