/* ============================================================================
   PEPJUNKIE DESIGN SYSTEM — v0.1 prototype constitution
   ----------------------------------------------------------------------------
   Operator brief section 3: "Scientific exploration × deep-space navigation ×
   molecular biology", "80% calm, 20% magic", and four explicit bans:
     NO overwhelming neon · NO generic pharma-white corporate · NO bodybuilding
     black-and-red · NO generic SaaS dashboard.

   Every one of those bans is a real constraint on this file:
     * the dark ground is DEEP NAVY-VOID #070b1c, never #000 — pure black plus
       glow is exactly the OLED/neon look he banned;
     * there is ONE saturated accent used sparingly (the 20%), not a palette of
       them;
     * the light mode is warm laboratory PAPER, not corporate white;
     * the layout is editorial with a reading measure, not a card-grid console.

   TYPE, looked up before it was invented (estate rule 230, UI/UX Pro Max):
     Exo          — from the verified "Science/Tech" pairing. Display and
                    headings. Carries the deep-space register without neon.
     Roboto Mono  — same verified pairing. Data, labels, and peptide sequences,
                    which are literally monospace data and have a real job here.
     Public Sans  — from the verified "Magazine Style" pairing. BODY only.
                    Chosen over that pairing's own partner and over the
                    Inter-everywhere default because this product is long-form
                    reading (Atlas pages, Paper Autopsies, arguments) and
                    because Inter-everything IS the generic SaaS look he banned.
   Two verified library pairings composed, with the reason stated, rather than
   one taken whole and quietly bent.

   MOTION: reveal by TRANSFORM, never by opacity. Text is legible at every
   frame. Everything honours prefers-reduced-motion.
   ============================================================================ */

@import url('https://fonts.googleapis.com/css2?family=Exo:ital,wght@0,400;0,500;0,600;0,700;0,800;1,400&family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=Roboto+Mono:wght@300;400;500;700&display=swap');

/* FALLBACK FACES MATCHED TO THE WEB FONTS' WIDTH (2026-09-27). Until the web
   fonts arrive the browser draws the page in a local face; Arial is a different
   width from Exo and Public Sans, so text wrapped one way and re-wrapped when the
   real face landed - the nav dropped to a second line and snapped back, a layout
   shift of 0.38 at 1440px and 0.43 at 1280px (the limit is 0.1). Each fallback is
   the local face scaled to the width the web font measured in Edge, mixed case,
   2026-09-27: Exo 400-500 = 103.5% of Arial, Exo 600-800 = 97.3% of Arial Bold,
   Public Sans 103.8% / 97.9%, Roboto Mono = Courier New. Arial is named per
   weight because it has only two faces: at 600 the browser switches to Arial
   Bold, and at 800 Windows substitutes Arial Black, which is far wider. Every
   stack that names a web font names its fallback next (tests/test_masthead_fit.py). */
@font-face{font-family:"Exo Fallback";src:local("Arial");font-weight:100 549;size-adjust:103.5%}
@font-face{font-family:"Exo Fallback";src:local("Arial Bold"),local("Arial-BoldMT");font-weight:550 900;size-adjust:97.3%}
@font-face{font-family:"Public Sans Fallback";src:local("Arial");font-weight:100 549;size-adjust:103.8%}
@font-face{font-family:"Public Sans Fallback";src:local("Arial Bold"),local("Arial-BoldMT");font-weight:550 900;size-adjust:97.9%}
@font-face{font-family:"Roboto Mono Fallback";src:local("Courier New");font-weight:100 549;size-adjust:100%}
@font-face{font-family:"Roboto Mono Fallback";src:local("Courier New Bold"),local("CourierNewPS-BoldMT");font-weight:550 900;size-adjust:100%}

/* ---------------------------------------------------------------- TOKENS */
:root{
  /* DARK — the default. Deep-space navigation, calm. */
  --void:        #070b1c;
  --surface:     #10162e;
  --surface-2:   #18203f;
  --surface-3:   #212c53;
  --line:        #2a3660;
  --line-soft:   #1e2748;
  --text:        #e9edfa;
  --text-2:      #b3bddd;
  --text-3:      #7e8ab0;

  /* THE 20% MAGIC — one saturated accent, used sparingly and on purpose. */
  --ion:         #63c7d6;   /* primary interactive: links, focus, active */
  --ion-dim:     #2d7f8d;
  --flare:       #e8a33d;   /* discovery / new / "this changed" */
  --plasma:      #9b8ce0;   /* speculation, the unknown */

  /* EVIDENCE TIERS — brief section 6. Six, and NEVER blended into one blob. */
  --ev-established: #3fa796;
  --ev-emerging:    #63c7d6;
  --ev-preclinical: #e8a33d;
  --ev-community:   #9b8ce0;
  --ev-speculation: #d9788f;
  --ev-unknown:     #7c88ae;

  /* HEAT — PACIFIER's emotional temperature. Never red-alert: this is a
     conversation, not an incident. */
  --heat-calm:  #3fa796;
  --heat-warm:  #e8a33d;
  --heat-hot:   #d9788f;

  --radius:   10px;
  --radius-s: 6px;
  --measure:  68ch;
  --shell:    1180px;

  --ease: cubic-bezier(.22,.61,.36,1);
  color-scheme: dark;
}

/* LIGHT — "a beautiful scientific journal/laboratory environment", his words.
   Warm paper, not corporate white. */
[data-theme="light"]{
  --void:        #f6f4ef;
  --surface:     #ffffff;
  --surface-2:   #f1efe8;
  --surface-3:   #e7e4da;
  --line:        #d6d1c4;
  --line-soft:   #e5e1d6;
  --text:        #151a27;
  --text-2:      #464e63;
  --text-3:      #6d7489;

  --ion:         #1d6f7d;
  --ion-dim:     #a8d4dc;
  --flare:       #a2651a;
  --plasma:      #5f4ba8;

  --ev-established: #1f6f62;
  --ev-emerging:    #1d6f7d;
  --ev-preclinical: #a2651a;
  --ev-community:   #5f4ba8;
  --ev-speculation: #a63b55;
  --ev-unknown:     #5b6479;

  --heat-calm: #1f6f62;
  --heat-warm: #a2651a;
  --heat-hot:  #a63b55;
  color-scheme: light;
}

/* ---------------------------------------------------------------- BASE */
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0;
  background:var(--void);
  color:var(--text);
  font:16px/1.68 "Public Sans","Public Sans Fallback","Segoe UI",system-ui,-apple-system,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;
}
/* The starfield: two barely-there radial washes. This is the "deep space"
   without a single star sprite or neon glow. */
body::before{
  content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:
    radial-gradient(1100px 620px at 78% -8%, rgba(99,199,214,.09), transparent 62%),
    radial-gradient(820px 520px at 8% 102%, rgba(155,140,224,.08), transparent 60%);
}
[data-theme="light"] body::before{
  background:
    radial-gradient(1100px 620px at 78% -8%, rgba(29,111,125,.07), transparent 62%),
    radial-gradient(820px 520px at 8% 102%, rgba(95,75,168,.05), transparent 60%);
}
h1,h2,h3,h4,.display{font-family:"Exo","Exo Fallback","Segoe UI",system-ui,sans-serif;letter-spacing:-.015em;font-weight:700;margin:0}
h1{font-size:clamp(28px,4.2vw,44px);line-height:1.1}
h2{font-size:clamp(21px,2.5vw,28px);line-height:1.2}
h3{font-size:18px;line-height:1.3}
p{margin:0 0 15px;max-width:var(--measure)}
a{color:var(--ion);text-decoration:none}
a:hover{text-decoration:underline}
:focus-visible{outline:2px solid var(--ion);outline-offset:3px;border-radius:3px}
.mono,code,kbd{font-family:"Roboto Mono","Roboto Mono Fallback",ui-monospace,Consolas,monospace}
.skip-link{position:absolute;top:0;left:0;padding:10px 16px;background:var(--surface);
  color:var(--text);z-index:200;transform:translateY(-130%)}
.skip-link:focus{transform:none}
.shell{max-width:var(--shell);margin:0 auto;padding:0 22px}
.measure{max-width:var(--measure)}
main{display:block}

/* ---------------------------------------------------------------- PROTOTYPE BANNER
   These screens carry illustrative content. His brief section 7 is explicit:
   never populate with fake humans pretending to have real experiences. This
   band says so on every screen so nothing here can be mistaken for community
   data. It is a prototype label, not a product disclaimer. */
.proto-band{
  background:var(--surface-2);border-bottom:1px solid var(--line);
  font:500 11.5px/1.5 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.06em;
  color:var(--text-3);text-align:center;padding:7px 16px;text-transform:uppercase;
}
.proto-band b{color:var(--flare);font-weight:700}

/* ---------------------------------------------------------------- MASTHEAD */
.mast{position:sticky;top:0;z-index:100;background:color-mix(in srgb,var(--void) 88%,transparent);
  backdrop-filter:blur(12px);border-bottom:1px solid var(--line-soft)}
.mast-in{max-width:var(--shell);margin:0 auto;padding:12px 22px;display:flex;align-items:center;gap:18px}
.brand{display:flex;align-items:center;gap:11px;flex:none}
.brand-mark{width:34px;height:34px;flex:none}
.brand-name{font:800 19px/1 "Exo","Exo Fallback",sans-serif;letter-spacing:-.02em;color:var(--text)}
.brand-name span{color:var(--ion)}
.brand-sub{display:block;font:400 10px/1.4 "Roboto Mono","Roboto Mono Fallback",monospace;color:var(--text-3);
  letter-spacing:.14em;text-transform:uppercase;margin-top:3px}
/* The nav NEVER wraps: a second line is a taller masthead, and a masthead that
   changes height moves every pixel of the page below it. Where the seven do not
   fit on one line, they move into the drawer (768-1099 below) instead. */
.mast nav{margin-left:auto;display:flex;gap:2px;flex-wrap:nowrap}
.mast nav a{font:600 12.5px/1 "Exo","Exo Fallback",sans-serif;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap;
  color:var(--text-2);padding:9px 11px;border-radius:var(--radius-s);text-decoration:none}
.mast nav a:hover{color:var(--text);background:var(--surface-2);text-decoration:none}
.mast nav a[aria-current="page"]{color:var(--ion);background:var(--surface-2)}
.themer{flex:none;margin-left:6px;background:var(--surface-2);border:1px solid var(--line);
  color:var(--text-2);font:600 11px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.08em;
  padding:9px 11px;border-radius:var(--radius-s);cursor:pointer;text-transform:uppercase}
.themer:hover{color:var(--text);border-color:var(--ion-dim)}

/* ---------------------------------------------------------------- BLOCKS */
.panel{background:var(--surface);border:1px solid var(--line-soft);border-radius:var(--radius);padding:20px 22px}
.panel + .panel{margin-top:16px}
.panel-h{display:flex;align-items:baseline;gap:12px;margin:0 0 14px}
.panel-h h2,.panel-h h3{margin:0}
.panel-h .more{margin-left:auto;font:600 11.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.06em;text-transform:uppercase}
.kicker{font:700 11px/1.5 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.17em;text-transform:uppercase;color:var(--ion);margin:0 0 10px}
.kicker.flare{color:var(--flare)}
.lede{font-size:18px;color:var(--text-2);max-width:60ch;margin:0 0 18px}
.grid{display:grid;gap:16px}
.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g3{grid-template-columns:repeat(3,minmax(0,1fr))}
/* 2026-09-24 (operator, Atlas curate panel): a <select> takes its width from its
   longest option and a form laid out as a grid gives its one column min-content,
   so a long claim spilled across the neighbouring column and a note ran under a
   button. Every form control is capped at its box and every grid form gets a
   column that can shrink; nothing may overlap a neighbour again. */
select,input,textarea{max-width:100%;min-width:0;box-sizing:border-box}
select{width:100%;text-overflow:ellipsis}
form{grid-template-columns:minmax(0,1fr)}
.g-main{grid-template-columns:minmax(0,1fr) 330px;align-items:start}
/* A grid child defaults to min-width:auto, which is min-content — so a long
   citation or a wide table can push a '1fr' column past the viewport and the
   whole document scrolls sideways. minmax(0,1fr) plus min-width:0 is the fix;
   measured at 390px, where this blew out to a 419px document. */
@media (max-width:960px){.g-main,.g3,.g2{grid-template-columns:minmax(0,1fr)}}
.grid > *{min-width:0}
.post > *{min-width:0}
/* Identifiers and citations are long unbroken-looking tokens. Let them break
   rather than widen the page. */
.mono,code,.seq{overflow-wrap:anywhere}
.rule{height:1px;background:var(--line-soft);border:0;margin:26px 0}

/* chips + badges ---------------------------------------------------------- */
.chip{display:inline-flex;align-items:center;gap:6px;font:500 11.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;
  letter-spacing:.04em;color:var(--text-2);background:var(--surface-2);
  border:1px solid var(--line);border-radius:999px;padding:6px 11px}
.chip b{color:var(--text);font-weight:700}
.chips{display:flex;flex-wrap:wrap;gap:8px;padding:0;margin:0;list-style:none}

.ev{display:inline-flex;align-items:center;gap:6px;font:700 10px/1 "Roboto Mono","Roboto Mono Fallback",monospace;
  letter-spacing:.09em;text-transform:uppercase;padding:5px 9px;border-radius:4px;
  color:var(--void);white-space:nowrap}
[data-theme="light"] .ev{color:#fff}
.ev::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor;opacity:.55}
.ev-established{background:var(--ev-established)}
.ev-emerging{background:var(--ev-emerging)}
.ev-preclinical{background:var(--ev-preclinical)}
.ev-community{background:var(--ev-community)}
.ev-speculation{background:var(--ev-speculation)}
.ev-unknown{background:var(--ev-unknown)}

/* the sequence, rendered as the data it is ------------------------------- */
.seq{font-family:"Roboto Mono","Roboto Mono Fallback",monospace;font-size:15px;letter-spacing:.14em;word-break:break-all;
  background:var(--surface-2);border:1px solid var(--line);border-left:3px solid var(--ion);
  border-radius:var(--radius-s);padding:12px 14px;color:var(--text)}
.seq .hi{color:var(--flare)}

/* buttons ----------------------------------------------------------------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  font:700 13px/1 "Exo","Exo Fallback",sans-serif;letter-spacing:.03em;padding:12px 18px;
  border-radius:var(--radius-s);border:1px solid var(--line);background:var(--surface-2);
  color:var(--text);cursor:pointer;text-decoration:none;min-height:44px}
.btn:hover{border-color:var(--ion-dim);text-decoration:none}
.btn-primary{background:var(--ion);border-color:var(--ion);color:var(--void);font-weight:800}
[data-theme="light"] .btn-primary{color:#fff}
.btn-primary:hover{filter:brightness(1.08)}
.btn-ghost{background:transparent}
.btn-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px}

/* posts ------------------------------------------------------------------- */
.post{display:grid;grid-template-columns:46px minmax(0,1fr);gap:14px;padding:18px 0;border-top:1px solid var(--line-soft)}
.post:first-of-type{border-top:0}
.av{width:46px;height:46px;border-radius:12px;background:var(--surface-3);
  display:grid;place-items:center;font:700 15px/1 "Exo","Exo Fallback",sans-serif;color:var(--text-2);flex:none}
.post-meta{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:7px;
  font:500 12px/1 "Roboto Mono","Roboto Mono Fallback",monospace;color:var(--text-3)}
.post-meta .who{color:var(--text);font-weight:700;font-family:"Exo","Exo Fallback",sans-serif;font-size:14px}
.post p{margin:0 0 11px}
.post p:last-child{margin:0}
.tag{font:700 9.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.09em;text-transform:uppercase;
  padding:4px 7px;border-radius:3px;border:1px solid var(--line);color:var(--text-3)}
.tag.sci{color:var(--ev-established);border-color:var(--ev-established)}
.tag.staff{color:var(--flare);border-color:var(--flare)}
.tag.founding{color:var(--plasma);border-color:var(--plasma)}

/* PACIFIER / system interjections ---------------------------------------- */
.pacifier{border:1px solid var(--line);border-left:3px solid var(--ion);
  background:linear-gradient(90deg,color-mix(in srgb,var(--ion) 9%,transparent),transparent 40%);
  border-radius:var(--radius-s);padding:15px 17px;margin:16px 0}
.pacifier .who{font:700 11px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.13em;text-transform:uppercase;
  color:var(--ion);display:flex;align-items:center;gap:8px;margin-bottom:9px}
.pacifier.warm{border-left-color:var(--flare);background:linear-gradient(90deg,color-mix(in srgb,var(--flare) 10%,transparent),transparent 40%)}
.pacifier.warm .who{color:var(--flare)}
.pacifier.hot{border-left-color:var(--heat-hot);background:linear-gradient(90deg,color-mix(in srgb,var(--heat-hot) 10%,transparent),transparent 40%)}
.pacifier.hot .who{color:var(--heat-hot)}
.pacifier p{font-size:15px;margin:0 0 10px;max-width:62ch}
.pacifier p:last-child{margin:0}

/* meters ------------------------------------------------------------------ */
.meter{height:7px;border-radius:99px;background:var(--surface-3);overflow:hidden}
.meter i{display:block;height:100%;border-radius:99px;background:var(--ion)}
.meter.warm i{background:var(--flare)}
.meter.hot i{background:var(--heat-hot)}
.stat{display:flex;flex-direction:column;gap:3px}
.stat .n{font:800 26px/1 "Exo","Exo Fallback",sans-serif;color:var(--text)}
.stat .l{font:500 10.5px/1.4 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.09em;text-transform:uppercase;color:var(--text-3)}

/* tables ------------------------------------------------------------------ */
.tscroll{overflow-x:auto}
table{width:100%;border-collapse:collapse;font-size:14px}
th,td{text-align:left;padding:11px 12px;border-bottom:1px solid var(--line-soft);vertical-align:top}
th{font:700 10.5px/1.3 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.08em;text-transform:uppercase;color:var(--text-3)}
caption{caption-side:bottom;text-align:left;font-size:12.5px;color:var(--text-3);padding-top:9px;font-style:italic}

/* MOTION — transform only. Never opacity: an opacity reveal renders text
   invisible, which is the exact shape of the hidden-text signal. */
@media (prefers-reduced-motion: no-preference){
  .rise{animation:rise .6s var(--ease) both}
  .rise-2{animation-delay:.07s}.rise-3{animation-delay:.14s}.rise-4{animation-delay:.21s}
  @keyframes rise{from{transform:translateY(14px)}to{transform:none}}
  .panel,.btn,.chip,.mast nav a{transition:border-color .2s var(--ease),background .2s var(--ease),transform .2s var(--ease),color .2s var(--ease)}
  .panel:hover{border-color:var(--line)}
  .chip:hover{transform:translateY(-2px)}
  /* The orbit: the one piece of ambient motion on the site. It conveys the
     constellation idea and nothing else moves. */
  .orbit{animation:orbit 34s linear infinite;transform-origin:center}
  @keyframes orbit{to{transform:rotate(360deg)}}
}
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;
    transition-duration:.001ms!important;scroll-behavior:auto!important}
}

footer.foot{border-top:1px solid var(--line-soft);margin-top:44px;background:var(--surface)}
.foot-in{max-width:var(--shell);margin:0 auto;padding:26px 22px 40px;color:var(--text-3);font-size:13px}
.foot-in a{color:var(--text-2)}

@media (max-width:720px){
  .mast{position:static}
  .mast-in{flex-wrap:wrap;gap:12px;padding:11px 18px}
  .mast nav{margin-left:0;width:100%;border-top:1px solid var(--line-soft);padding-top:9px;gap:0}
  .mast nav a{padding:8px 9px;font-size:11.5px}
  .themer{margin-left:auto}
  .panel{padding:17px 16px}
  body{font-size:15.5px}
}

/* ============================================================================
   MOTION LAYER — "make PepJunkie feel alive" (operator, 2026-09-19)
   ----------------------------------------------------------------------------
   "Stop showing only what PepJunkie LOOKS like. Show what it FEELS like when
   alive." And the guard rail that stops it becoming a casino: "80% calm, 20%
   magic ... not animations everywhere."

   The product already communicates its NOUNS — the Pulse, Hunter-Gatherer,
   Post-Match Autopsy. What it did not communicate are its VERBS: arguing,
   discovering, counting, detecting, heating up, being watched, being
   remembered. Everything below exists to show a verb.

   THE 20% IS SPENT DELIBERATELY. Motion appears where it CARRIES INFORMATION:
   a number arriving, a proportion settling, a temperature rising, a machine
   gathering. Decoration that carries nothing is the thing he banned, so pages
   stay still where people read.

   Driven by pepjunkie-motion.js, which holds every animation until its element
   is on screen and honours prefers-reduced-motion by doing NOTHING. The final
   state of every element is in the markup, so the page is complete and correct
   with no JavaScript at all.
   ============================================================================ */

/* ------------------------------------------------------------------ NUMBERS */
/* A value that ARRIVES rather than one merely printed. The tabular figures
   matter more than they look: without them the digits change width as they
   climb and the whole line jitters, which reads as cheap. */
.pj-num{font-family:"Roboto Mono","Roboto Mono Fallback",monospace;font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum" 1;color:var(--text);font-weight:700;
  display:inline-block;transform:translateZ(0)}
.pj-num.pj-counting{color:var(--ion)}
/* The settle: 420ms of scale, once, as the value lands. This one gesture is
   the difference between a number that changed and a number that ARRIVED. */
.pj-num.pj-settled{animation:pj-settle 420ms cubic-bezier(.22,1.2,.36,1) both}
@keyframes pj-settle{
  0%{transform:scale(1.055)}
  60%{transform:scale(.995)}
  100%{transform:scale(1)}
}
.pj-stat{display:flex;flex-direction:column;gap:3px}
.pj-stat .pj-num{font-size:30px;letter-spacing:-.02em;line-height:1.05}
.pj-stat small{font:600 10.5px/1.4 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.14em;
  text-transform:uppercase;color:var(--text-3)}
.pj-statrow{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}
@media (max-width:720px){.pj-statrow{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}}

/* ------------------------------------------------------------- PROPORTIONS */
/* scaleX from a left origin, never width: animating width reflows the document
   on every single frame. */
.pj-bar{position:relative;height:7px;border-radius:99px;background:var(--surface-3);
  overflow:hidden}
.pj-bar-fill{position:absolute;inset:0;border-radius:99px;transform-origin:left center;
  background:linear-gradient(90deg,var(--ion-dim),var(--ion))}
.pj-bar-fill.is-warm{background:linear-gradient(90deg,#8a6420,var(--flare))}
.pj-bar-fill.is-hot{background:linear-gradient(90deg,#8d4456,var(--heat-hot))}
.pj-bar-fill.is-good{background:linear-gradient(90deg,#24645a,var(--ev-established))}
.pj-bar-fill.is-plasma{background:linear-gradient(90deg,#5b4f92,var(--plasma))}
.pj-bar-row{display:grid;grid-template-columns:minmax(96px,160px) 1fr auto;gap:12px;
  align-items:center;margin:10px 0}
.pj-bar-row .lab{font:600 12px/1.3 "Roboto Mono","Roboto Mono Fallback",monospace;color:var(--text-2)}
.pj-bar-row .val{font:700 12.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;color:var(--text);
  font-variant-numeric:tabular-nums;min-width:46px;text-align:right}

/* ---------------------------------------------------------------- ASSEMBLY */
/* Children arrive in sequence. The stagger is the whole reason a list reads as
   being GATHERED rather than displayed. Text arrives by TRANSFORM and is
   legible at every frame — it never fades up out of nothing. */
.pj-assemble > *{will-change:transform}
.pj-assembling > *{animation:pj-arrive 620ms cubic-bezier(.16,1,.3,1) backwards}
.pj-assembled > *{animation:none;transform:none;will-change:auto}
@keyframes pj-arrive{
  from{transform:translateY(9px) scale(.985)}
  to{transform:none}
}
/* `backwards` is not a detail. Without it a staggered child renders in its
   FINAL position during its delay and then jumps back to the start when the
   animation begins. Every stagger looks broken without that one word. */

/* --------------------------------------------------------------- EVENT TAPE */
/* "more 'right now' signals ... they must feel like events." A tape that
   scrolls and STOPS the moment a reader hovers or tabs into it — a strip
   somebody is trying to read must not move. */
.pj-tape{position:relative;display:flex;overflow:hidden;
  border-top:1px solid var(--line-soft);border-bottom:1px solid var(--line-soft);
  background:linear-gradient(180deg,var(--surface),var(--surface-2));
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent);
  mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent)}
.pj-tape-track{display:flex;flex:none;align-items:center;gap:30px;padding:10px 15px;
  white-space:nowrap}
.pj-tape-run .pj-tape-track{animation:pj-tape 46s linear infinite}
/* Pause for a reader who is pointing or tabbing at the strip. Hover only where a pointer can
   really hover: on a phone a tap leaves :hover stuck on the strip until the next tap elsewhere,
   which froze the strip for good (operator, DuckDuckGo Android, 2026-10-01). */
@media (hover:hover){.pj-tape:hover .pj-tape-track{animation-play-state:paused}}
.pj-tape:focus-within .pj-tape-track{animation-play-state:paused}
@keyframes pj-tape{from{transform:translateX(0)}to{transform:translateX(-100%)}}
.pj-tape-item{display:inline-flex;align-items:center;gap:9px;
  font:500 12.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;color:var(--text-2)}
.pj-tape-item b{color:var(--text);font-weight:700}
.pj-tag{font:700 9.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.13em;
  text-transform:uppercase;padding:4px 7px;border-radius:4px;
  background:var(--surface-3);color:var(--text-3)}
.pj-tag.new{background:color-mix(in srgb,var(--flare) 22%,var(--surface-3));color:var(--flare)}
.pj-tag.hot{background:color-mix(in srgb,var(--heat-hot) 22%,var(--surface-3));color:var(--heat-hot)}
.pj-tag.ok{background:color-mix(in srgb,var(--ev-established) 22%,var(--surface-3));color:var(--ev-established)}
.pj-tag.think{background:color-mix(in srgb,var(--plasma) 22%,var(--surface-3));color:var(--plasma)}

/* ------------------------------------------------------------- LIVE SIGNALS */
/* One event at a time, replaced on a slow cycle. The old line leaves upward
   and the new one enters from below, so the strip reads as a feed ADVANCING
   rather than as text being swapped out. */
/* The live line and the event tape are two DIFFERENT signals - one personal,
   one global - and stacked flush against each other they read as one block of
   clutter. Seen in the browser at 1280, not reasoned about. */
.pj-live{position:relative;overflow:hidden;height:23px;margin-top:20px}
.pj-tape{margin-top:10px}
.pj-live-item{position:absolute;inset:0;display:flex;align-items:center;gap:9px;
  transform:translateY(108%);transition:transform 640ms cubic-bezier(.16,1,.3,1);
  font:500 12.5px/1.6 "Roboto Mono","Roboto Mono Fallback",monospace;color:var(--text-2);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.pj-live-item.is-on{transform:none}
.pj-live-item b{color:var(--text)}
.pj-dot{width:7px;height:7px;border-radius:50%;background:var(--ion);flex:none;
  color:color-mix(in srgb,var(--ion) 55%,transparent);
  animation:pj-ping 2.6s ease-out infinite}
.pj-dot.flare{background:var(--flare);color:color-mix(in srgb,var(--flare) 55%,transparent)}
.pj-dot.hot{background:var(--heat-hot);color:color-mix(in srgb,var(--heat-hot) 55%,transparent)}
.pj-dot.good{background:var(--ev-established);color:color-mix(in srgb,var(--ev-established) 55%,transparent)}
/* A decorative ring may use opacity. WORDS may not — that is the ban. */
@keyframes pj-ping{
  0%{box-shadow:0 0 0 0 currentColor}
  70%{box-shadow:0 0 0 9px transparent}
  100%{box-shadow:0 0 0 0 transparent}
}

/* -------------------------------------------------------------------- HEAT */
/* "When a topic is hot, it should LOOK hot" — premium, not a cartoon flame
   slapped on a row. Three devices, all driven by one --heat variable (0..1):
   a thermal edge that travels, embers that drift, an intensity meter. */
.pj-heat{position:relative;--heat:.5}
.pj-heat::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;
  border-radius:3px;
  background:linear-gradient(180deg,var(--heat-calm),var(--heat-warm),var(--heat-hot));
  background-size:100% 220%;animation:pj-thermal 5.2s ease-in-out infinite alternate}
@keyframes pj-thermal{from{background-position:0 0}to{background-position:0 100%}}
.pj-heat.is-calm::before{background:var(--heat-calm);animation:none}

/* Embers, capped at three per topic on purpose: this is an accent, and the
   fourth one is where taste ends and a screensaver begins. */
.pj-embers{position:absolute;left:0;top:0;bottom:0;width:26px;pointer-events:none;
  overflow:hidden}
.pj-ember{position:absolute;bottom:2px;width:3px;height:3px;border-radius:50%;
  background:var(--heat-warm);opacity:0;box-shadow:0 0 6px currentColor;
  color:var(--heat-warm);animation:pj-rise 4.6s ease-in infinite}
.pj-ember:nth-child(1){left:7px;animation-delay:0s}
.pj-ember:nth-child(2){left:14px;animation-delay:1.5s;background:var(--heat-hot)}
.pj-ember:nth-child(3){left:10px;animation-delay:2.9s}
/* Travel far enough to read as heat LEAVING something. At 56px inside a
   165px panel they lived in the bottom third and looked like specks of dust -
   seen in the browser, not reasoned about. */
@keyframes pj-rise{
  0%{transform:translateY(0) scale(.75);opacity:0}
  14%{opacity:.9}
  70%{opacity:.5}
  100%{transform:translateY(-104px) translateX(7px) scale(.3);opacity:0}
}
.pj-heat-meter{display:inline-flex;align-items:center;gap:7px;
  font:700 10px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.12em;text-transform:uppercase}
.pj-heat-track{width:54px;height:5px;border-radius:99px;background:var(--surface-3);
  overflow:hidden;position:relative}
.pj-heat-fill{position:absolute;inset:0;transform-origin:left center;
  transform:scaleX(var(--heat));
  background:linear-gradient(90deg,var(--heat-calm),var(--heat-warm),var(--heat-hot));
  animation:pj-breathe 3.1s ease-in-out infinite alternate}
@keyframes pj-breathe{from{transform:scaleX(calc(var(--heat) * .9))}
                      to{transform:scaleX(var(--heat))}}
.pj-heat-label.hot{color:var(--heat-hot)}
.pj-heat-label.warm{color:var(--heat-warm)}
.pj-heat-label.calm{color:var(--heat-calm)}

/* ---------------------------------------------------------------- SCANNING */
/* Hunter-Gatherer must not read as a search box. "It should feel like the
   machine is doing work" — so a sweep traverses the surface while results
   assemble beneath it. */
.pj-scan{position:relative;overflow:hidden}
.pj-scan::after{content:"";position:absolute;left:-40%;top:0;bottom:0;width:40%;
  background:linear-gradient(90deg,transparent,
    color-mix(in srgb,var(--ion) 14%,transparent),transparent);
  animation:pj-sweep 2.6s cubic-bezier(.5,0,.5,1) infinite;pointer-events:none}
@keyframes pj-sweep{from{transform:translateX(0)}to{transform:translateX(350%)}}
.pj-scan.is-done::after{display:none}

/* Signal nodes clustering out of noise. */
/* SVG, not positioned divs. The div version mixed percentage width against
   percentage height in a 640x104 box, so every connector was drawn at an angle
   that could not reach its node - visible immediately in the browser, invisible
   in the code. One viewBox, one coordinate system, problem gone. */
.pj-cluster{display:block;width:100%;height:104px;border-radius:var(--radius-s);
  background:
    radial-gradient(60% 80% at 22% 50%,color-mix(in srgb,var(--ion) 7%,transparent),transparent),
    var(--surface-2);
  border:1px solid var(--line-soft);overflow:hidden}
.pj-node{position:absolute;width:9px;height:9px;border-radius:50%;
  background:var(--ion);transform:translate(-50%,-50%) scale(0);
  animation:pj-node-in 700ms cubic-bezier(.16,1,.3,1) forwards}
.pj-node.n-preclinical{background:var(--ev-preclinical)}
.pj-node.n-community{background:var(--ev-community)}
.pj-node.n-established{background:var(--ev-established)}
.pj-node.n-speculation{background:var(--ev-speculation)}
@keyframes pj-node-in{
  from{transform:translate(-50%,-50%) scale(0)}
  60%{transform:translate(-50%,-50%) scale(1.4)}
  to{transform:translate(-50%,-50%) scale(1)}
}
.pj-link{position:absolute;height:1px;transform-origin:left center;
  background:linear-gradient(90deg,color-mix(in srgb,var(--ion) 50%,transparent),transparent);
  animation:pj-link-in 900ms cubic-bezier(.16,1,.3,1) backwards}
@keyframes pj-link-in{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* ------------------------------------------------------------------ REVEAL */
/* The Post-Match Autopsy is a crown jewel and earns its own register: staged,
   deliberate, one beat at a time, like an episode rather than a text box. */
.pj-reveal > *{animation:pj-reveal-in 720ms cubic-bezier(.16,1,.3,1) backwards}
@keyframes pj-reveal-in{
  from{transform:translateY(15px) scale(.99)}
  to{transform:none}
}
.pj-verdict{position:relative;overflow:hidden}
.pj-verdict::before{content:"";position:absolute;inset:0;
  background:radial-gradient(120% 90% at 50% 0%,
    color-mix(in srgb,var(--plasma) 17%,transparent),transparent 62%);
  animation:pj-verdict-glow 4.8s ease-in-out infinite alternate;pointer-events:none}
@keyframes pj-verdict-glow{from{transform:translateY(-6px) scale(1)}
                           to{transform:translateY(0) scale(1.05)}}
.pj-verdict > *{position:relative;z-index:1}

/* ---------------------------------------------------------- REACTIVE CARDS */
/* Hover reveals INFORMATION, not decoration: the card lifts and a hidden
   detail row slides into the space. Touch devices get the detail permanently,
   because there is no hover to give them and hiding it would simply delete
   content on a phone. */
.pj-card{transition:transform 260ms cubic-bezier(.16,1,.3,1),border-color 260ms ease;
  will-change:transform}
.pj-card:hover,.pj-card:focus-within{transform:translateY(-3px);border-color:var(--ion-dim)}
.pj-peek{max-height:0;overflow:hidden;transition:max-height 320ms cubic-bezier(.16,1,.3,1)}
.pj-card:hover .pj-peek,.pj-card:focus-within .pj-peek{max-height:90px}
@media (hover:none){
  .pj-peek{max-height:none}
  .pj-card:hover{transform:none}
}

/* --------------------------------------------------------------- AMBIENCE */
/* A molecular lattice drifting behind the hero. Deliberately near-invisible:
   this is atmosphere, and the moment you can consciously SEE it, it is too
   strong and it is competing with the words. */
.pj-ambient{position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0}
.pj-ambient i{position:absolute;width:2px;height:2px;border-radius:50%;
  background:var(--ion);opacity:.2;animation:pj-drift 26s linear infinite}
@keyframes pj-drift{
  from{transform:translate3d(0,0,0)}
  to{transform:translate3d(32px,-44px,0)}
}
.pj-has-ambient{position:relative}
.pj-has-ambient > *:not(.pj-ambient){position:relative;z-index:1}

/* ------------------------------------------------------- ILLUSTRATIVE LABEL */
/* Brief section 7: never populate with fake humans pretending to have real
   experiences. MOTION MAKES INVENTED ACTIVITY LOOK MORE REAL, NOT LESS, so the
   honesty label gets stronger here rather than quieter. Anything that moves as
   though it were live says, in words, that it is not. */
.pj-illus{font:600 9.5px/1.4 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.13em;
  text-transform:uppercase;color:var(--text-3)}
.pj-illus::before{content:"\25C7 ";color:var(--flare)}

/* ------------------------------------------------------------------ MOBILE */
@media (max-width:720px){
  .pj-stat .pj-num{font-size:24px}
  .pj-bar-row{grid-template-columns:1fr auto;gap:7px}
  .pj-bar-row .pj-bar{grid-column:1 / -1}
  .pj-tape-track{gap:20px;padding:9px 12px}
  .pj-cluster{height:86px}
  .pj-embers{width:18px}
}

/* prefers-reduced-motion is handled twice on purpose: the blanket rule earlier
   in this file kills durations, and pepjunkie-motion.js refuses to start at
   all. Either alone is enough; both together mean a bug in one cannot put
   motion in front of somebody who asked for none. */
@media (prefers-reduced-motion: reduce){
  .pj-tape-run .pj-tape-track,.pj-ember,.pj-dot,.pj-scan::after,.pj-heat::before,
  .pj-heat-fill,.pj-ambient i,.pj-verdict::before,.pj-node,.pj-link{animation:none!important}
  .pj-node{transform:translate(-50%,-50%) scale(1)}
  .pj-link{transform:scaleX(1)}
  .pj-live{height:auto}
  .pj-live-item{transform:none;position:relative;white-space:normal}
  .pj-live-item:not(.is-on){display:none}
  .pj-peek{max-height:none}
}

/* ------------------------------------------------------- REACTIVE CHOICES */
/* Onboarding's own copy promises "every answer changes the next screen
   immediately". A revealed panel therefore ARRIVES rather than appearing: the
   movement is the acknowledgement that the choice registered. */
.chip[data-choice]{transition:transform 200ms cubic-bezier(.16,1,.3,1),
  border-color 200ms ease,color 200ms ease;user-select:none}
.chip[data-choice]:hover{transform:translateY(-2px);border-color:var(--ion-dim);color:var(--text)}
.chip[data-choice].is-chosen{border-color:var(--ion);color:var(--text);
  background:color-mix(in srgb,var(--ion) 12%,var(--surface-2))}
.chip[data-choice]:focus-visible{outline:2px solid var(--ion);outline-offset:2px}
.pj-swap.pj-swapped{animation:pj-swap-in 480ms cubic-bezier(.16,1,.3,1) backwards}
@keyframes pj-swap-in{from{transform:translateY(8px)}to{transform:none}}
@media (prefers-reduced-motion: reduce){
  .pj-swap.pj-swapped{animation:none}
  .chip[data-choice]:hover{transform:none}
}

.pj-edge{stroke:var(--ion);stroke-width:1;stroke-opacity:.45;
  stroke-dasharray:1;stroke-dashoffset:1;
  animation:pj-draw 900ms cubic-bezier(.16,1,.3,1) forwards}
@keyframes pj-draw{to{stroke-dashoffset:0}}
.pj-dot-svg{fill:var(--ion);transform-box:fill-box;transform-origin:center;
  transform:scale(0);animation:pj-pop 700ms cubic-bezier(.16,1,.3,1) forwards}
.pj-dot-svg.n-preclinical{fill:var(--ev-preclinical)}
.pj-dot-svg.n-community{fill:var(--ev-community)}
.pj-dot-svg.n-established{fill:var(--ev-established)}
.pj-dot-svg.n-speculation{fill:var(--ev-speculation)}
@keyframes pj-pop{60%{transform:scale(1.4)}to{transform:scale(1)}}
@media (prefers-reduced-motion: reduce){
  .pj-edge{stroke-dashoffset:0;animation:none}
  .pj-dot-svg{transform:scale(1);animation:none}
}

/* ============================================================================
   RESPONSIVE CONSTITUTION — operator, 2026-09-19
   ----------------------------------------------------------------------------
   "'does not horizontally scroll at 390px' is not the same thing as 'designed
   properly for phones' ... Mobile must not be desktop but narrow."

   He is right and the previous pass earned the criticism: it proved an ABSENCE
   (nothing overflows) and reported it as a PRESENCE (designed for phones).
   Those are different claims and only one of them was true.

   FIVE DELIBERATE COMPOSITIONS, not one layout that shrinks:

     PHONE          <= 599    one column, bottom navigation, the intelligence
                              sidebar becomes a swipeable card rail, density
                              drops, concurrent live signals are reduced
     LARGE PHONE    600-767   same composition, more generous measure, two-up
                              statistics
     TABLET PORTRAIT 768-1023 top navigation returns, two columns where the
                              second column earns its place, larger touch
                              targets than desktop because it is still a hand
     TABLET LANDSCAPE 1024-1279  full two-column intelligence layout at tablet
                              density
     DESKTOP        >= 1280   the composition the screens were authored in

   PLUS A SIXTH THAT IS NOT A WIDTH: phone landscape (844x390, 932x430) is
   wide enough to look like a tablet and short enough that a tablet layout is
   wrong. It is detected by HEIGHT and orientation, not by width, and it
   flattens vertical rhythm so something other than a hero is on screen.

   PORTRAIT IS A FIRST-CLASS FORMAT, not an inconvenience: the Autopsy
   re-stages vertically into a rail of beats, which is MORE theatrical than the
   desktop composition rather than a compressed version of it.
   ============================================================================ */

:root{
  /* Named, so a breakpoint is a decision with a name rather than a number
     somebody guessed. */
  --bp-phone:      599px;
  --bp-phone-lg:   767px;
  --bp-tablet:     1023px;
  --bp-tablet-lg:  1279px;
  /* Minimum comfortable thumb target. 44px is the figure both platform
     guidelines converge on, and it is a floor, not an aspiration. */
  --tap: 44px;
  --gutter: 22px;
}

/* Fluid type. clamp() means there is no viewport where the heading is absurd,
   and no cliff at a breakpoint where it jumps. */
h1{font-size:clamp(28px, 6.2vw, 52px);line-height:1.08}
h2{font-size:clamp(20px, 3.2vw, 27px)}
h3{font-size:clamp(16px, 2.2vw, 19px)}
.lede{font-size:clamp(15.5px, 2.1vw, 18px)}
.shell{padding-left:var(--gutter);padding-right:var(--gutter)}

/* Nothing may be wider than its column. The single most common cause of a
   phone scrolling sideways is a descendant with an intrinsic minimum. */
.grid > *,.post > *,.panel > *{min-width:0}
img,svg,video,canvas{max-width:100%}
.seq,.mono,code,pre{overflow-wrap:anywhere}

/* ------------------------------------------------- DYNAMIC VIEWPORT HEIGHT */
/* Mobile browser chrome appears and disappears, and 100vh is the height as if
   it never existed -- so a 100vh panel is taller than the screen for most of
   a session. svh is the small (chrome-visible) height; dvh follows the chrome
   live. Declared with a vh fallback first for anything that predates them. */
.pj-screen{min-height:100vh;min-height:100svh}
.pj-sheet{max-height:80vh;max-height:80dvh}

/* ============================================================== PHONE <=767 */
@media (max-width:767px){
  :root{--gutter:16px}

  /* ONE COLUMN, and the page is not simply every desktop panel stacked into a
     twelve-metre scroll: the sidebar is recomposed below, density drops here. */
  .g-main,.g3,.g2{grid-template-columns:minmax(0,1fr)}
  .grid{gap:12px}
  .panel{padding:16px 15px}
  .panel + .panel{margin-top:12px}
  body{font-size:15.5px}

  /* THE INTELLIGENCE SIDEBAR BECOMES A SWIPEABLE RAIL. Stacking four context
     panels under a long feed buries them where nobody scrolls. A horizontal
     rail with scroll snapping is a native phone gesture, keeps every panel
     reachable, and costs no vertical height. */
  .g-main > aside{
    display:grid;grid-auto-flow:column;grid-auto-columns:86%;
    gap:12px;overflow-x:auto;overscroll-behavior-x:contain;
    scroll-snap-type:x mandatory;
    /* bleed to the screen edges so the next card peeks, which is what tells a
       thumb there is more to the right */
    margin:4px calc(var(--gutter) * -1) 0;
    padding:0 var(--gutter) 10px;
    scrollbar-width:none}
  .g-main > aside::-webkit-scrollbar{display:none}
  .g-main > aside > .panel{scroll-snap-align:start;margin-top:0;min-width:0}
  .g-main > aside::before{
    content:"Swipe for context \\2192";grid-column:1 / -1;
    font:600 9.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.14em;
    text-transform:uppercase;color:var(--text-3);
    position:absolute;transform:translateY(-16px)}
  .g-main{position:relative}

  /* TOUCHABILITY. Not "technically clickable". */
  .btn,.chip[data-choice],.themer,.mast nav a,button,[role="button"]{
    min-height:var(--tap);display:inline-flex;align-items:center;
    justify-content:center}
  .btn{padding:12px 18px;font-size:14.5px}
  .btn-row{gap:10px}
  .btn-row .btn{flex:1 1 auto;min-width:140px}
  input[type="range"]{height:var(--tap)}

  /* LIVE SIGNALS: FEWER, NOT SMALLER. Three tickers competing on a 360px strip
     is visual noise pretending to be life. */
  .pj-tape-item:nth-child(n+5){display:none}
  .pj-tape-track{gap:18px}
  .pj-live-item{font-size:12px}

  /* HEAT: THE ARGUMENT SHOULD FEEL HOT, THE PHONE SHOULD NOT. One ember, less
     glow, no breathing meter -- three glowing particles per card across a long
     feed is both visual dirt and a GPU bill on a mid-range device. */
  .pj-ember:nth-child(n+2){display:none}
  .pj-ember{box-shadow:none}
  .pj-heat-fill{animation:none}
  .pj-dot{animation-duration:3.4s}

  /* Ambience is a desktop luxury. On a phone it is eight more composited
     layers for something nobody can consciously see. */
  .pj-ambient{display:none}

  .pj-statrow{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
  .pj-stat .pj-num{font-size:23px}
  .pj-bar-row{grid-template-columns:1fr auto;gap:6px}
  .pj-bar-row .pj-bar{grid-column:1 / -1;height:8px}

  /* Hover-only content is content a phone does not have. */
  .pj-peek{max-height:none}
  .pj-card:hover{transform:none}
}

/* ------------------------------------------------ SMALL PHONE <=380 (320px) */
@media (max-width:380px){
  :root{--gutter:13px}
  .panel{padding:14px 13px}
  .pj-statrow{gap:10px}
  .pj-stat .pj-num{font-size:20px}
  .pj-stat small{font-size:9.5px;letter-spacing:.1em}
  .chips{gap:6px}
  .chip{font-size:11px;padding:7px 9px}
  .g-main > aside{grid-auto-columns:90%}
  /* 320px is the floor. Below this the product is not the problem. */
  .pj-tape-item:nth-child(n+4){display:none}
}

/* ================================================== LARGE PHONE 600 - 767 */
@media (min-width:600px) and (max-width:767px){
  :root{--gutter:20px}
  .pj-statrow{grid-template-columns:repeat(4,1fr)}
  .g2{grid-template-columns:repeat(2,minmax(0,1fr))}
  .g-main > aside{grid-auto-columns:62%}
}

/* ============================================= TABLET PORTRAIT 768 - 1023 */
/* A tablet is neither a big phone nor a small desktop. The second column earns
   its place back, but touch targets stay generous because it is still a hand
   holding a sheet of glass. */
@media (min-width:768px) and (max-width:1023px){
  :root{--gutter:26px}
  .g-main{grid-template-columns:minmax(0,1fr) 290px;align-items:start}
  .g3{grid-template-columns:repeat(2,minmax(0,1fr))}
  .panel{padding:19px 20px}
  .btn{min-height:42px;padding:11px 17px}
  .mast nav a{padding:11px 11px}
  .pj-statrow{grid-template-columns:repeat(4,1fr)}
  /* The Atlas, Hunter-Gatherer and Paper Autopsy are the screens that gain
     most from this width: comparison side by side rather than in sequence. */
  .tscroll table{font-size:14px}
}

/* ============================================ TABLET LANDSCAPE 1024 - 1279 */
@media (min-width:1024px) and (max-width:1279px){
  :root{--gutter:28px}
  .g-main{grid-template-columns:minmax(0,1fr) 310px}
  .panel{padding:20px 21px}
}

/* ================================================ PHONE LANDSCAPE (height) */
/* 844x390 and 932x430 are WIDE. By width alone they are tablets, and a tablet
   layout on a 390px-tall viewport puts a hero where the whole page should be.
   Detected by height and orientation, never by width. */
@media (orientation:landscape) and (max-height:500px){
  section.shell{padding-top:20px!important;padding-bottom:16px!important}
  h1{font-size:clamp(22px,3.4vw,30px)}
  .lede{font-size:14.5px;max-width:66ch}
  .panel{padding:14px 16px}
  .mast{position:static}
  .mast-in{padding:8px 16px}
  .pj-stat .pj-num{font-size:21px}
  .pj-cluster{height:74px}
  .pj-ambient{display:none}
  /* The bottom bar costs a fifth of a 390px-tall screen. In landscape the top
     bar is the cheaper home for navigation. */
  .pj-bottomnav{display:none!important}
  body{padding-bottom:0!important}
  .pj-tape-item:nth-child(n+6){display:none}
}

/* ===================================================== MOBILE NAVIGATION */
/* Desktop navigation squeezed into a phone header is the thing he asked us not
   to do. Five primary destinations at the bottom, where a thumb is, with
   Hunter-Gatherer given the centre and extra weight because it is a flagship
   and must never be buried in a menu. Everything else lives in a drawer built
   from <details>, which is native, keyboard-operable and needs no JavaScript. */
.pj-bottomnav{display:none}

@media (max-width:767px){
  .pj-bottomnav{
    display:grid;grid-template-columns:repeat(5,1fr);
    position:fixed;left:0;right:0;bottom:0;z-index:200;
    background:color-mix(in srgb,var(--void) 92%,transparent);
    backdrop-filter:blur(10px);
    border-top:1px solid var(--line);
    /* Nothing important sits under the home indicator. */
    padding-bottom:env(safe-area-inset-bottom,0px)}
  .pj-bottomnav a{
    display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:3px;min-height:var(--tap);padding:8px 2px 7px;
    font:700 9.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.06em;
    text-transform:uppercase;color:var(--text-3);text-decoration:none;
    -webkit-tap-highlight-color:transparent}
  .pj-bottomnav a svg{width:21px;height:21px;stroke:currentColor;fill:none;
    stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
  .pj-bottomnav a[aria-current="page"]{color:var(--ion)}
  .pj-bottomnav a:active{background:var(--surface-2)}
  /* The flagship gets the centre and a lifted marker. */
  .pj-bottomnav .is-flagship{color:var(--text)}
  .pj-bottomnav .is-flagship svg{
    background:linear-gradient(160deg,var(--ion-dim),var(--ion));
    color:var(--void);border-radius:12px;padding:5px;width:31px;height:31px;
    margin-top:-13px;box-shadow:0 5px 14px -6px var(--ion)}
  .pj-bottomnav .is-flagship[aria-current="page"] svg{
    box-shadow:0 5px 18px -4px var(--ion)}

  /* Room for the bar, plus the device's own inset. */
  body{padding-bottom:calc(64px + env(safe-area-inset-bottom,0px))}

  /* The masthead keeps brand, theme and the drawer only. */
  .mast nav{display:none}
  .mast-in{gap:10px;padding:10px var(--gutter);flex-wrap:nowrap}
  .brand-sub{display:none}
  .pj-more{margin-left:auto}
}

/* Tablets and small laptops, 768-1099 (measured 2026-09-27): the full masthead
   needs 1,034px even at the compact nav metrics - brand 192, nav 517, search 175,
   theme 52 - and the fallback face is up to 7% wider for the moment before Exo
   lands. Below 1,100px (1,034 + a desktop scrollbar + that margin) the seven
   destinations go into the same drawer the phone uses, instead of wrapping
   (1024-1152 grew the masthead 68 -> 88px) or pushing the page 941px wide
   (768-940: a sideways scroll on every tablet). Landscape phones land here too. */
@media (min-width:768px) and (max-width:1099px){
  .mast nav{display:none}
}

/* The drawer: secondary destinations, native disclosure, no script. */
.pj-more{display:none}
@media (max-width:1099px){
  .pj-more{display:block;position:relative}
  .pj-more > summary{
    list-style:none;cursor:pointer;min-height:var(--tap);min-width:var(--tap);
    display:flex;align-items:center;justify-content:center;
    border:1px solid var(--line);border-radius:var(--radius-s);
    background:var(--surface-2);color:var(--text-2);padding:0 12px;
    font:700 10.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.1em;
    text-transform:uppercase}
  .pj-more > summary::-webkit-details-marker{display:none}
  .pj-more[open] > summary{color:var(--text);border-color:var(--ion-dim)}
  .pj-more-panel{
    position:absolute;right:0;top:calc(100% + 8px);z-index:210;
    min-width:min(70vw,250px);
    background:var(--surface);border:1px solid var(--line);
    border-radius:var(--radius);padding:7px;
    box-shadow:0 18px 40px -18px rgba(0,0,0,.8)}
  .pj-more-panel a{
    display:flex;align-items:center;min-height:var(--tap);padding:0 12px;
    border-radius:var(--radius-s);color:var(--text-2);text-decoration:none;
    font:600 13px/1 "Exo","Exo Fallback",sans-serif;letter-spacing:.04em}
  .pj-more-panel a[aria-current="page"]{color:var(--ion);background:var(--surface-2)}
  .pj-more-panel a:active{background:var(--surface-2)}
}

/* ============================================ PORTRAIT AUTOPSY, RE-STAGED */
/* "A portrait Post-Match Autopsy could actually be MORE theatrical than
   desktop if designed correctly. Use the format instead of fighting it."
   So on a narrow screen the argument becomes a vertical rail of beats with a
   thermal spine running through it -- scrolling the credits of the stupidest
   scientific war ever conducted. */
.pj-stage{position:relative}
.pj-stage-beat{position:relative}
@media (max-width:767px){
  .pj-stage{padding-left:26px}
  .pj-stage::before{
    content:"";position:absolute;left:8px;top:6px;bottom:6px;width:2px;
    border-radius:2px;
    background:linear-gradient(180deg,var(--heat-calm),var(--heat-warm) 55%,
      var(--heat-hot) 74%,var(--heat-calm));
    background-size:100% 260%;
    animation:pj-thermal 7s ease-in-out infinite alternate}
  .pj-stage-beat{margin:0 0 22px}
  .pj-stage-beat::before{
    content:"";position:absolute;left:-22px;top:9px;width:9px;height:9px;
    border-radius:50%;background:var(--surface-3);
    border:2px solid var(--ion);box-sizing:content-box}
  .pj-stage-beat.is-hot::before{border-color:var(--heat-hot);
    background:color-mix(in srgb,var(--heat-hot) 45%,var(--surface-3))}
  .pj-stage-beat > h2{margin-top:0!important;
    font:700 12px/1.3 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.16em;
    text-transform:uppercase;color:var(--ion)}
  .pj-stage-beat.is-hot > h2{color:var(--heat-hot)}
  .pj-stage-beat > p,.pj-stage-beat > ul{margin-top:7px}
}

/* =========================================== HUNTER-GATHERER, NARROW WIDTHS */
/* "Do not simply scale a 640px intelligence visual down until labels become
   microscopic." preserveAspectRatio="none" stretched the cluster; at 360px the
   nodes squashed into a flat line that said nothing. Below the tablet
   threshold the cluster keeps its aspect, grows taller, and drops to a
   readable subset of nodes. */
@media (max-width:767px){
  .pj-cluster{height:132px}
  /* Fewer, larger nodes: eight tiny dots on a 340px strip is noise. */
  .pj-cluster .pj-node-opt{display:none}
  .pj-cluster circle{r:5.5}
  .pj-edge{stroke-width:1.4;stroke-opacity:.55}
}
@media (max-width:380px){
  .pj-cluster{height:118px}
}

/* ================================================================ SAFE AREAS */
/* Notches, islands and gesture bars. Applied to the fixed surfaces only --
   padding the whole body for a notch would indent every page on every device. */
@supports (padding:max(0px)){
  @media (max-width:767px){
    .mast-in{
      padding-left:max(var(--gutter),env(safe-area-inset-left));
      padding-right:max(var(--gutter),env(safe-area-inset-right))}
    .pj-bottomnav{
      padding-left:env(safe-area-inset-left);
      padding-right:env(safe-area-inset-right)}
  }
  @media (orientation:landscape) and (max-height:500px){
    .shell{
      padding-left:max(var(--gutter),env(safe-area-inset-left));
      padding-right:max(var(--gutter),env(safe-area-inset-right))}
  }
}

/* ======================================================= TEXT THAT GROWS */
/* "Do not assume everybody sees exactly our authored font size." Nothing that
   carries meaning may be given a fixed height, and a long molecule name, a
   long handle or a translated label must wrap rather than be truncated into
   nonsense. */
.chip,.ev,.pj-tag,.who,.brand-name{overflow-wrap:anywhere}
.chip{height:auto;min-height:0}
.pj-stat small{white-space:normal}
.pj-bar-row .lab{overflow-wrap:anywhere;white-space:normal}
/* The live strip is the one place a single line is required, because the items
   slide over one another. It gets a real ellipsis rather than a clip, and its
   height grows with the text instead of cropping it. */
.pj-live{height:auto;min-height:23px}
.pj-live-item{position:absolute;inset:0}

/* ================================================== PERFORMANCE DISCIPLINE */
/* will-change is a promise to the compositor and each one costs a layer. It is
   removed the moment the element has finished arriving; leaving dozens
   promised is how a prototype murders a mid-range phone. */
.pj-assembled > *{will-change:auto}
@media (max-width:767px){
  .pj-card{will-change:auto}
  /* Backdrop blur is the single most expensive effect here. One surface gets
     it on a phone, not two. */
  .mast{backdrop-filter:none;background:var(--void)}
}
@media (prefers-reduced-motion: reduce){
  .pj-stage::before{animation:none}
  .g-main > aside{scroll-snap-type:none}
}

/* ------------------------------------------- TOUCH FLOORS, MEASURED FIXES */
/* Both of these were found by measuring every interactive element at every
   viewport in the matrix, not by looking at the code.

   1. THE BRAND LOGO WAS 34px TALL. It is the "go home" control on every screen
      and it sat a third below the thumb floor on every phone in the matrix.
   2. PHONE LANDSCAPE HAD NO TOUCH FLOORS AT ALL. 844x390 and 932x430 are wider
      than 767px, so every rule in the phone block simply did not apply and a
      touch device was served desktop-sized controls -- 9 to 15 undersized
      targets per screen. A landscape phone is still a phone; it is identified
      by height, and so are its touch floors. */
@media (max-width:767px), (orientation:landscape) and (max-height:500px){
  .brand{min-height:var(--tap);align-items:center}
  .themer,.pj-more > summary{min-height:var(--tap)}
}
@media (orientation:landscape) and (max-height:500px){
  /* Vertical space is scarce here, so targets grow sideways rather than down:
     the floor is met by padding-inline and a min-height that costs 44px once,
     in the header, rather than on every row. */
  .btn{min-height:var(--tap);padding:9px 18px;display:inline-flex;
    align-items:center;justify-content:center}
  .chip[data-choice]{min-height:var(--tap);display:inline-flex;align-items:center}
  .mast nav a{min-height:var(--tap);display:inline-flex;align-items:center}
  input[type="range"]{height:var(--tap)}
  .pj-more > summary{min-width:var(--tap)}
}

/* ------------------------------------ THE LAST FIVE UNDERSIZED TAP TARGETS */
/* Found by measuring EVERY interactive element at every viewport in the
   matrix. Two classes, both real navigation, both fixed differently on
   purpose:

   .more  ("Open the Atlas -->", "All discussions -->") measured 12px and 35px.
          These are standalone actions in a panel header, so they simply take
          padding and grow.

   breadcrumbs measured 15px. A 44px-tall breadcrumb row would dominate a phone
          header, so the LINK keeps its size and its HIT AREA is expanded with
          a transparent pseudo-element. The target is thumb-sized; the type is
          not. This is the right trade -- growing the text would cost layout
          that the breadcrumb does not deserve. */
@media (max-width:767px), (orientation:landscape) and (max-height:500px){
  .panel-h .more{
    display:inline-flex;align-items:center;min-height:var(--tap);
    padding:0 2px;margin:-8px 0}

  nav[aria-label="Breadcrumb"] a,.crumbs a,main > section nav a{
    position:relative;display:inline-block}
  nav[aria-label="Breadcrumb"] a::after,.crumbs a::after,main > section nav a::after{
    content:"";position:absolute;left:-5px;right:-5px;
    top:calc((var(--tap) - 100%) / -2);bottom:calc((var(--tap) - 100%) / -2);
    /* the expander must never swallow the NEXT link's area */
    pointer-events:auto}
  /* Breathing room so two expanded hit areas cannot overlap and cause a
     mis-tap, which would be a worse defect than the one being fixed. */
  nav[aria-label="Breadcrumb"],.crumbs,main > section nav{
    display:flex;flex-wrap:wrap;align-items:center;gap:2px 10px;
    padding:6px 0}
}

/* ------------------------------------------- TWO THINGS SEEN AT 360, FIXED */
/* 1. THE HONESTY BAND ATE FOUR LINES of the first screen at 360px. It must
      stay -- it is the one thing standing between an invented community and a
      lie -- but it can be denser. Smaller, tighter, and the separators wrap
      instead of forcing long unbreakable runs. */
@media (max-width:767px){
  .proto-band{font-size:9.5px;line-height:1.5;letter-spacing:.08em;
    padding:7px 12px;text-align:left}
}
@media (max-width:420px){
  .proto-band{font-size:9px;letter-spacing:.05em;padding:6px 11px}
}

/* 2. THE LIVE LINE CLIPPED MID-WORD with no ellipsis. A later rule in the
      text-growth section re-declared .pj-live-item for absolute positioning
      and dropped the truncation properties with it -- the classic cascade
      accident, invisible in the code and obvious on the screen. Restored
      here, after both, so it cannot be lost again. */
.pj-live-item{
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  display:flex;align-items:center;gap:9px}
.pj-live-item > *:not(.pj-dot){overflow:hidden;text-overflow:ellipsis}
@media (prefers-reduced-motion: reduce){
  .pj-live-item{white-space:normal;overflow:visible}
}

/* ----------------------------------------- TWO THINGS SEEN AT 768, FIXED */
/* 1. THE TOP NAVIGATION WRAPPED TO TWO LINES at tablet portrait, pushing the
      theme button up beside a half-empty row. Seven destinations at desktop
      metrics do not fit 768px, so at this band the nav tightens rather than
      wrapping -- the same seven items, one line.
      2026-09-27: the search box joined the masthead after this was written and
      the tightened nav no longer fitted 768-1023 (the page went 941px wide), so
      below 1,100px the nav now lives in the drawer (see the drawer block); the
      tightened metrics apply where the nav IS shown and still needs them,
      1100-1279, which otherwise wrapped to two lines. */
@media (min-width:768px) and (max-width:1279px){
  .mast nav{gap:0;flex-wrap:nowrap}
  .mast nav a{padding:10px 8px;font-size:11.5px;letter-spacing:.04em}
  .brand-sub{font-size:9px}
}

/* 2. THE FIVE HEADLINE STATISTICS BROKE 4 + 1, orphaning "4 Post-Match
      Autopsies" alone on its own row where it read as an afterthought rather
      than the fifth of five. The minimum column drops so all five sit on one
      line at tablet, and the group becomes a deliberate two-row block on a
      phone instead of an accident. */
@media (min-width:768px) and (max-width:1279px){
  .panel > .grid[style*="auto-fit"]{
    grid-template-columns:repeat(5,minmax(0,1fr))!important;gap:12px}
  .panel > .grid[style*="auto-fit"] .pj-stat .pj-num{font-size:25px}
  .panel > .grid[style*="auto-fit"] .pj-stat small{font-size:9.5px;letter-spacing:.09em}
}
@media (max-width:767px){
  .panel > .grid[style*="auto-fit"]{
    grid-template-columns:repeat(2,minmax(0,1fr))!important}
}

/* ------------------------------------ PHONE LANDSCAPE: EVERY LINE IS 1/12th */
/* Seen at 844x390: the masthead wrapped two destinations onto a second line
   and the honesty band took two more, so a third of a 390px-tall screen was
   spent before any content. On a short viewport vertical space is the scarce
   resource and chrome must earn every pixel of it. */
@media (orientation:landscape) and (max-height:500px){
  .mast nav{flex-wrap:nowrap;gap:0}
  .mast nav a{white-space:nowrap;padding:10px 7px;font-size:11px;
    letter-spacing:.03em}
  .brand-sub{display:none}
  .brand-name{font-size:17px}
  .proto-band{font-size:9px;letter-spacing:.05em;padding:5px 12px;
    white-space:nowrap;overflow-x:auto;scrollbar-width:none}
  .proto-band::-webkit-scrollbar{display:none}
  .pj-live{margin-top:12px}
  .pj-tape{margin-top:8px}
}

/* ---------------------------- THE CLUSTER'S BOX MUST BE THE CLUSTER'S SHAPE */
/* Seen at 320px: preserveAspectRatio="meet" correctly scaled the 640x104
   graphic to fit the width, and then LETTERBOXED it inside a fixed 118px box,
   leaving 33px of dead navy above and below. The fix is not to stretch the
   graphic back -- that was the original defect -- it is to stop asserting a
   height the graphic does not have. aspect-ratio makes the container adopt the
   drawing's proportions at every width, so there is never dead space and never
   distortion. Nodes grow as the drawing shrinks, so the trace stays readable
   rather than becoming a row of specks. */
.pj-cluster{width:100%;height:auto;aspect-ratio:640 / 104}
@media (max-width:767px){
  .pj-cluster{aspect-ratio:640 / 104;height:auto}
  .pj-cluster circle{r:7}
  .pj-edge{stroke-width:1.6;stroke-opacity:.6}
}
@media (max-width:420px){
  .pj-cluster circle{r:8}
  .pj-edge{stroke-width:1.9}
}
@media (orientation:landscape) and (max-height:500px){
  .pj-cluster{aspect-ratio:640 / 104;height:auto}
}

/* A plain labelled row -------------------------------------------------------
   Added 2026-09-20 because there was no such thing and I reached for
   `.pj-live-item`, which is a ticker item: `position:absolute; inset:0` inside
   a 23px window. Three amendments rendered on top of one another as an
   unreadable smear running off the page. The class EXISTED, so the guard that
   checks classes against this stylesheet passed it - a class that exists but
   means something else is still the wrong class, and only looking at the page
   showed it. */
.pj-row{display:block;padding:9px 0;border-bottom:1px solid var(--line-soft)}
.pj-row:last-child{border-bottom:0}
.pj-row b{color:var(--text)}
.pj-row code{margin-right:8px}
.pj-row .more{display:block;margin-top:3px}

/* Search (product surface, 2026-09-23): a masthead box on wide screens; the drawer carries the link below 767px. */
.pj-search{display:flex;align-items:center;gap:4px;margin-left:auto}
.pj-search input{width:150px;padding:6px 10px;border-radius:999px;border:1px solid var(--line);background:var(--surface-2);color:var(--text);font:inherit;font-size:13px}
.pj-search button{border:0;background:transparent;color:var(--text-2);font-size:16px;cursor:pointer;padding:4px 6px}
@media (max-width:767px){.pj-search{display:none}}

/* ================================================================== MASCOT */
/* docs/MASCOT_SPEC_2026-09-26.md section 6. A mark reserves his box from the
   still's own proportions (--pj-w / --pj-h, written by the server), so his
   arrival, every performance and his departure shift nothing. The stage, the
   bubble and his buttons live absolutely inside it; only his body - the poke
   button - takes the pointer, so nothing underneath him is unclickable.
   His IMAGE may cross-fade (decorative: law 4 of the motion engine); his
   WORDS never fade - they arrive by transform and read at every frame. */
.pj-mark{--pj-mh:260px;position:relative;flex:none;z-index:0;
  height:var(--pj-mh);width:calc(var(--pj-mh) * var(--pj-w,411) / var(--pj-h,520))}
.pj-mark--section{--pj-mh:200px}
.pj-mark--inline{--pj-mh:160px}
.pj-mark--right{margin-left:auto}
.pj-mark.is-hidden{visibility:hidden}
@media (max-width:1023px){
  .pj-mark{--pj-mh:200px}
  .pj-mark--section{--pj-mh:160px}
  .pj-mark--inline{--pj-mh:140px}
}
/* Phones: he stays where he stands, small. The dock shows only where nothing
   protected is under it, and on a phone, where text runs the full width, that
   is almost never - measured on Home 2026-09-26: the dock sat on "The Atlas
   has no entries yet" and he never appeared at all. A page can still hand a
   mark to the dock: .pj-mark--dockable makes it a zero-size anchor there, and
   the Director docks any mark that renders at zero size. */
@media (max-width:599px){
  .pj-mark,.pj-mark--section,.pj-mark--inline{--pj-mh:120px}
  .pj-mark--dockable{height:0;width:0;margin:0}
  .pj-mark--dockable > *{display:none}
}
.pj-mark-inner{position:absolute;inset:0;transform-origin:50% 100%}
.pj-mark--right .pj-mark-inner,.pj-dock .pj-mark-inner{transform-origin:100% 100%}
.pj-mark--left .pj-mark-inner{transform-origin:0 100%}
.pj-mark-inner picture{display:block;width:100%;height:100%}
/* His still and his clip fill exactly the box they are given, whatever the rest
   of the stylesheet says about images, canvases and boxes (*{box-sizing:
   border-box}, canvas{max-width:100%}): padding, a border, a clamp, a fit or a
   transform would move where his pixels land. Measured 2026-09-26: the
   Autopsy rail's .pj-stage{padding-left:26px} reached his canvas, then called
   .pj-stage too, and drew him 25 % narrower on a phone the moment a clip
   began. Every class of his is his alone now (tests/test_mascot_css.py), and
   these resets win over any element rule by class. The stage is wider than
   his box by design - the reason for max-width:none. */
.pj-mascot-still{display:block;width:100%;height:100%;margin:0;padding:0;border:0;max-width:none;transform:none;
  object-fit:contain;object-position:50% 100%}
.pj-mascot-stage{position:absolute;z-index:1;display:block;margin:0;padding:0;border:0;box-sizing:content-box;
  max-width:none;max-height:none;min-width:0;min-height:0;transform:none;object-fit:fill;pointer-events:none}
.pj-mascot-stage--card{border-radius:var(--radius);box-shadow:0 14px 34px rgba(0,0,0,.28);animation:pj-in .2s ease-out both}
.pj-mark-inner.is-playing .pj-mascot-still{visibility:hidden}
.pj-mark-inner.is-carding .pj-mascot-still{animation:pj-out .2s ease-out both}
/* The way back to the still when a clip ends elsewhere: the clip fades OUT,
   then the still fades IN - never both at once, so two different poses are
   never on screen together (a cross-fade reads as a body changing shape). */
.pj-mark-inner.is-returning .pj-mascot-stage{animation:pj-out .16s ease-in both}
.pj-mark-inner.is-returning .pj-mascot-still{animation:pj-in .18s ease-out .16s both}
.pj-mark.is-away .pj-mark-inner,.pj-dock.is-away .pj-mark-inner{visibility:hidden}
@keyframes pj-in{from{opacity:0}to{opacity:1}}
@keyframes pj-out{from{opacity:1}to{opacity:0}}
.pj-mascot-hit{position:absolute;z-index:2;left:18%;right:18%;top:4%;bottom:0;margin:0;padding:0;border:0;
  background:transparent;cursor:pointer;border-radius:44% 44% 14px 14px;-webkit-tap-highlight-color:transparent}
.pj-mascot-hit:focus-visible{outline:2px solid var(--ion);outline-offset:3px}
/* His quiet switch stands OUTSIDE him, just above his head on his outer side -
   never on him (operator, 2026-09-26: on a phone the pill sat on half his head).
   Always shown on touch; with a mouse, on hover or focus. His balloons keep
   clear of it too. */
.pj-mascot-quiet{position:absolute;z-index:3;right:0;bottom:calc(100% + 6px);visibility:hidden;min-height:26px;
  white-space:nowrap;font:700 9.5px/1 "Roboto Mono","Roboto Mono Fallback",monospace;letter-spacing:.08em;text-transform:uppercase;cursor:pointer;
  color:var(--text-2);background:var(--surface-2);border:1px solid var(--line);border-radius:999px;padding:6px 9px}
.pj-mark--left .pj-mascot-quiet{right:auto;left:0}
.pj-mark:hover .pj-mascot-quiet,.pj-mark:focus-within .pj-mascot-quiet,
.pj-dock:hover .pj-mascot-quiet,.pj-dock:focus-within .pj-mascot-quiet{visibility:visible}
@media (hover:none){.pj-mascot-quiet{visibility:visible}}
/* His words as a comic balloon: a speech balloon when he talks, a thought
   cloud when he thinks. The Director draws the shape round the words it holds,
   aims it at his head and stands it beside or above him - never on him - so
   its place is set inline, never here. White and ink in both themes: it is a
   comic balloon, not a panel of the site. It pops out from its tail. */
.pj-bubble{position:absolute;z-index:4;display:flex;align-items:center;justify-content:center;box-sizing:border-box;
  width:max-content;margin:0;padding:0;border:0;background:none;color:#141414;pointer-events:none;
  filter:drop-shadow(0 5px 12px rgba(0,0,0,.26));visibility:hidden;transform:scale(.86);
  transition:transform .16s ease-in,visibility 0s linear .16s}
.pj-bubble.is-on{visibility:visible;transform:none;transition:transform .28s cubic-bezier(.2,1.35,.45,1),visibility 0s}
.pj-bubble-art{position:absolute;left:0;top:0;overflow:visible;pointer-events:none}
.pj-bubble-art path,.pj-bubble-art circle{fill:#fff;stroke:#141414;stroke-width:2.4;stroke-linejoin:round}
.pj-bubble-words{position:relative;display:block;max-width:190px;margin:0;text-align:center;color:#141414;
  font:700 13.5px/1.25 "Exo","Exo Fallback",system-ui,sans-serif;letter-spacing:.02em;text-transform:uppercase}
.pj-bubble.is-lettered .pj-bubble-words{font:700 15px/1.2 "Comic Neue","Exo","Exo Fallback",system-ui,sans-serif;letter-spacing:.01em}
@media (max-width:599px){
  .pj-bubble-words{font-size:12.5px}
  .pj-bubble.is-lettered .pj-bubble-words{font-size:14px}
}
.pj-said{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
/* Gestures move him by the individual transform properties - a nod, a lean,
   a perk - and never change his size or proportions. */
.pj-mark-inner.is-g-nod{animation:pj-g-nod .4s ease-in-out}
.pj-mark-inner.is-g-lean{animation:pj-g-lean .6s ease-in-out}
.pj-mark--left .pj-mark-inner.is-g-lean{animation-name:pj-g-lean-r}
.pj-mark-inner.is-g-perk{animation:pj-g-perk .3s ease-out}
@keyframes pj-g-nod{40%{translate:0 4px}}
@keyframes pj-g-lean{45%{rotate:-1.5deg}}
@keyframes pj-g-lean-r{45%{rotate:1.5deg}}
@keyframes pj-g-perk{40%{translate:0 -4px}}
/* Where marks sit on a page: beside the hero copy, and floated into a panel
   the text wraps around. A panel that holds one contains its float. */
.pj-hero-row{display:flex;align-items:flex-end;gap:28px}
.pj-hero-copy{flex:1;min-width:0}
.pj-mark-float{float:right;margin:4px 0 12px 20px}
.panel.pj-has-mark{display:flow-root}
@media (max-width:599px){
  .pj-mark-float{margin:2px 0 8px 12px}
  /* the hero on a phone: he stands first, top right, above the kicker */
  .pj-hero-row{flex-direction:column;align-items:stretch;gap:0}
  .pj-hero-row > .pj-mark{order:-1;align-self:flex-end;margin-bottom:12px}   /* his envelope clears the kicker */
}
/* The dock (spec 6.1, 6.6): bottom-right, above the bottom navigation; the
   Director shows it only where it covers nothing protected. */
.pj-dock{--pj-mh:96px;position:fixed;right:12px;bottom:calc(76px + env(safe-area-inset-bottom,0px));z-index:90;
  height:var(--pj-mh);width:calc(var(--pj-mh) * var(--pj-w,411) / var(--pj-h,520));visibility:hidden}
.pj-dock.is-on{visibility:visible}
@media (min-width:600px){.pj-dock{--pj-mh:120px}}
@media (min-width:768px){.pj-dock{bottom:16px}}
/* A hidden mark or a hidden dock hides everything of his, including the
   pieces that ask to be visible on their own. */
.pj-dock:not(.is-on) .pj-mascot-quiet,.pj-dock:not(.is-on) .pj-bubble,
.pj-mark.is-hidden .pj-mascot-quiet,.pj-mark.is-hidden .pj-bubble{visibility:hidden!important}
@media (prefers-reduced-motion: reduce){
  .pj-mark-inner,.pj-mascot-still,.pj-mascot-stage{animation:none!important}
  .pj-bubble,.pj-bubble.is-on{transition:none!important;transform:none!important}
}
