/* ==========================================================================
   neuralkatana.ai  ·  gate
   Values lifted from Shared/brand/kusanagi ink/kusanagi-ink.tokens.json
   and the v2 art-direction spec. Do not "improve" a number here.

   RESPONSIVE STRATEGY, stated once because it is unusual:
   type size is NOT set by media queries. Both lines are measured at runtime
   and solved to fit, because a 12-glyph display wordmark in a face with no
   web metrics cannot be trusted to a clamp(). CSS owns the CAPS and the
   edge margins; JS owns the solved size. See useFit() in app.jsx.
   ========================================================================== */

@font-face{
  font-family:'Mars';
  src:url('/Shared/fonts/Futuristic%20Mars/Mars.ttf') format('truetype');
  font-weight:400;font-display:block;
}
/* Ink rule: never let CJK fall through a Latin-only stack. Mars has no kana. */
@font-face{
  font-family:'Noto Sans JP';
  src:url('/Shared/fonts/Kusanagi-Ink/NotoSansJP-Regular.ttf') format('truetype');
  font-weight:400;font-display:block;
}
/* CJK DISPLAY. The Ink token file specifies Noto Serif JP for display kanji.
   NOTE: this file is 12.9 MB. Fine for dev, wrong for ship. Subset it to the
   used codepoints before this goes live.

   THIS NOTE IS THE MANIFEST until a real one exists, so the set is written
   down rather than left to be re-derived from a grep at ship time. It said
   "two glyphs" and has been wrong for a while: the study's five panel
   compounds, the two room names and the dismiss are all CJK now.

   Added 2026-08-17, the drill-in's dismiss:  U+7D0D U+5200  (納刀)
   Added 2026-08-17, the third room:        U+6F14 U+6B66 U+5834  (演武場)
   Both verified present in the shipped face before the control went in, so
   nothing tofus today; this is a debt for the subsetting pass, not a bug.

   Whoever does that pass: the used set is NOT guessable from the copy alone,
   because room.jsx builds some of it from data (the FACTS array's `k` field).
   Enumerate it from source, do not eyeball the rendered page. */
@font-face{
  font-family:'Noto Serif JP';
  src:url('/Shared/fonts/Kusanagi-Ink/NotoSerifJP-Regular.ttf') format('truetype');
  font-weight:400;font-display:swap;
}
/* the Ink technical face */
@font-face{
  font-family:'JetBrains Mono';
  src:url('/Shared/fonts/Kusanagi-Ink/JetBrainsMono-Regular.ttf') format('truetype');
  font-weight:400;font-display:block;
}
/* BOLD. The file was always on disk and was never declared, so any request for
   weight 700 was being FAUX-BOLDED off the regular: the browser smears the
   glyph sideways and the result is mush at display size. The closing lines are
   set in this, so it has to be the real cut. */
@font-face{
  font-family:'JetBrains Mono';
  src:url('/Shared/fonts/Kusanagi-Ink/JetBrainsMono-Bold.ttf') format('truetype');
  font-weight:700;font-display:block;
}

:root{
  --void:#0A0A0A;
  --headline:#f6f4f2;
  --muted:#93908e;
  --ink:#b4b1af;
  --ink-dim:#6e6b69;     /* LINEWORK/GLOSS ONLY. 2.93:1 on ground = fails. */
  --accent-red:#ff4b4b;  /* the thing that is alive. The gate spends it once. */

  /* type ramp steps the entry control needs. Restored: these were dropped in
     the React rewrite and the control referenced them, which would have
     rendered the charge bar with no colour at all. */
  --t-08:clamp(8px,0.600vw,11px);   /* fine-print stratum ONLY. Below the ramp
                                        on purpose: this register is meant to be
                                        scanned, not read at distance. */
  --t-09:clamp(9px,0.660vw,12px);   /* THE NAVIGATION REGISTER. Jon, 2026-08-24:
                                        the top menu links "need to be slightly
                                        bigger (not much)." One step above the
                                        fine print they used to share: +10% at
                                        1600 (9.6 -> 10.56), +9% at the 1920 cap
                                        (11 -> 12), +12% at the 1280 floor. A
                                        control is read, not scanned, so it sits
                                        between --t-08 and the body's --t-10. */
  --t-10:clamp(10px,0.833vw,15px);
  --t-12:clamp(12px,1.000vw,18px);
  --t-18:clamp(18px,1.500vw,27px);
  --t-24:clamp(24px,2.000vw,36px);

  --d-ui:240ms;
  --ease-ui:cubic-bezier(0.4,0,0.2,1);

  /* upper caps only. The solved size is min(cap, fits-in-viewport). */
  --mark-cap:84px;      /* --t-56 ceiling */
  /* THE BOOT COUNTDOWN, and it is derived from the wordmark rather than picked.
     The number is the only thing on the boot screen, so it carries the weight
     the wordmark carries a second later; twice the mark's ceiling is what makes
     the two read as the same instrument at different moments. Deriving it with
     calc() means one edit to --mark-cap moves both and the handoff cannot fall
     out of proportion. 12vw keeps it honest on a narrow viewport, and the 72px
     floor is where a two-digit value stops being legible. */
  /* Jon, 2026-08-23: "make the countdown numbers on the primary site load just
     a little bit smaller." 1.72x the mark instead of 2x, floor and vw brought
     down in the same ratio (72 -> 62, 12vw -> 10.3vw), so the derivation and
     the narrow-viewport behaviour both hold at the new size. */
  --nk-count-cap:clamp(62px,10.3vw,calc(var(--mark-cap) * 1.72));
  --tag-cap:18px;       /* --t-12 ceiling */

  --tag-track:.28em;
  --tag-word:.60em;
  --tag-gap:32px;
  --enter-gap:40px;   /* tagline baseline to the control */

  --edge:84px;          /* v2 grid outer margin, desktop */

  /* THE DRAFTING SHEET, and it is a token because it is on EVERY view now.
     Jon, 2026-08-15: "more faint, lines are too obvious. Have them on every
     page." The gate is transparent so it inherits from body, but the wall and
     the study both paint their own opaque void ground and would cover
     anything behind them, so they apply this too. One definition, three
     surfaces, no drift.

     ALPHAS ARE PRE-MULTIPLIED. They used to be .431 and .686 carried on a
     layer at opacity .045, which worked but meant the value on screen was a
     product of two numbers in two places. Baked here instead, so what is
     written IS what lands, and every surface gets it identically.

     .0105 and .017, down from an effective .0194 and .0309. Measured peak went
     17 of 255 to 8, which is the "too obvious" correction. */
  --sheet-minor:rgba(246,244,242,.0105);
  --sheet-major:rgba(246,244,242,.017);
  /* NO THIRD SHEET TOKEN. I added a --sheet-lamp here on 2026-08-17, reading
     "the grid with lights" as an invitation to draw lit intersections on the
     boot screen. Jon: "the grid for the front door count down needs to be
     EXACTLY THE SAME AS WHAT YOU'RE ALREADY USING EVERYWHERE ELSE." He was
     describing the sheet he already approved, not asking for a new treatment,
     and inventing one made the boot screen the only surface with its own grid.
     The whole point of this token is that there is one. */
  --sheet:
    repeating-linear-gradient(to right,  var(--sheet-major) 0 1px,
                                         transparent 1px 120px),
    repeating-linear-gradient(to bottom, var(--sheet-major) 0 1px,
                                         transparent 1px 120px),
    repeating-linear-gradient(to right,  var(--sheet-minor) 0 1px,
                                         transparent 1px 20px),
    repeating-linear-gradient(to bottom, var(--sheet-minor) 0 1px,
                                         transparent 1px 20px);
}

@media (max-width:1439px){ :root{--edge:48px} }
@media (max-width:1023px){ :root{--edge:32px} }
@media (max-width:767px) { :root{--edge:20px;--tag-gap:24px;--enter-gap:30px;--tag-track:.22em;--tag-word:.48em} }
@media (max-width:479px) { :root{--edge:16px} }

*,*::before,*::after{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;
  background:var(--void);
  color:var(--headline);
  -webkit-font-smoothing:antialiased;
  overflow:hidden;
}

#root{min-height:100%}

.gate{
  position:relative;            /* the board is absolutely placed inside it */
  min-height:100svh;            /* svh, so mobile browser chrome cannot clip it */
  min-height:100vh;             /* fallback first in source order below */
  display:grid;
  place-items:center;
  padding-inline:var(--edge);
  /* THE REGISTER'S LANE, RESERVED ONLY AS THE WINDOW SHORTENS.  Jon,
     2026-08-24: "the footer should not underlap or overlap any other element."
     The lockup is centred in the whole viewport and .fineprint is fixed at its
     foot, so at 700px tall ENTER touched the register, at 600 the label sat on
     it, at 500 the brain did (measured). app.jsx publishes the register's
     lane (--nk-fine-lane: height + bottom offset + 18) and the lockup's height
     (--nk-lockup-h); the bottom padding is exactly the shift that keeps the
     centred lockup's foot above the lane, and no more:
        p = lockup + 2 x lane - viewport, clamped to [0, lane]
     Zero at every height where the composition already clears (at 1000px it
     is zero, so the desktop composition does not move), the full lane once
     the lockup only just fits, and below that the window is shorter than
     lockup + lane and the rule at max-height below takes over. */
  --nk-gate-pad:clamp(0px, calc(var(--nk-lockup-h, 420px) + 2 * var(--nk-fine-lane, 154px) - 100vh), var(--nk-fine-lane, 154px));
  padding-bottom:var(--nk-gate-pad);
}
@supports (height:100svh){
  .gate{
    min-height:100svh;
    --nk-gate-pad:clamp(0px, calc(var(--nk-lockup-h, 420px) + 2 * var(--nk-fine-lane, 154px) - 100svh), var(--nk-fine-lane, 154px));
  }
}
/* NOTHING FITS: the register goes in flow and the page scrolls. Under this
   height the lockup plus the lane is taller than the window, so no shift can
   keep them apart; the only honest layout is a column. The register becomes
   the last grid item, the gate keeps centring the pair while there is room,
   and the document scrolls when there is not. */
@media (max-height:570px){
  html,body{ overflow:auto; height:auto }
  .gate{ padding-bottom:var(--edge); align-content:center }
  .gate .fineprint{
    position:static; order:2;      /* it precedes the lockup in the DOM */
    margin-top:clamp(24px,5vh,48px); justify-self:start; opacity:.80 }
  .gate.comp-c .fineprint{ justify-self:end }
}

.lockup{
  display:flex;
  flex-direction:column;
  align-items:center;
  max-width:100%;
  /* lifted above the board. An absolutely positioned element paints above
     static siblings whatever the document order, so without this the board
     would sit on top of the wordmark despite being rendered first. */
  position:relative;
  z-index:1;
}

/* --------------------------------------------------------------------------
   THE BOARD. Full-bleed circuit, generated in app.jsx and drawn once. This
   file only places it; everything about how it looks is in paintBoard().

   z-index 0 puts it under the lockup (1), the fine print (40), the grain
   (900) and the shatter canvas (920), which is the whole intent: it is the
   surface, not a layer of the composition.

   THE CIRCUIT IS GONE, THE SHEET STAYS.  Jon, 2026-08-15: "dimness is nice.
   Get rid of the circuit keep the crossed lines." So the extracted Pico 2
   routing came out and what is left is the graticule it was drawn on: a
   drafting sheet with nothing drafted on it yet, which suits a gate whose
   whole argument is withholding.

   NO IMAGE AT ALL NOW. With the traces gone the artwork is two pitches of
   straight line, and that is a gradient rather than a picture. Shipping a
   90 KB PNG of a grid would be paying an image request to rasterise something
   CSS draws exactly, and it would resample under background-size:cover on
   every viewport that is not 1440 wide. Gradients stay crisp at any DPI.

   THE NUMBERS ARE CARRIED OVER, NOT RECHOSEN, because Jon approved this
   dimness and a rebuild is not a licence to redecorate. The PNG was 2016 wide
   under cover at 1440, a scale of 0.714, so its 28px and 168px pitches landed
   on screen at 20px and 120px. Its alphas were 110 and 175 of 255, which are
   the .431 and .686 below. Same sheet, same weight, drawn instead of scaled.

   ONE REAL CHANGE, worth stating: the PNG's pitch BREATHED with the viewport
   because cover rescaled it, so the grid was 20px at 1440 and 36px at 2560.
   These are fixed. A drafting sheet has a pitch; it does not have a pitch that
   depends on how wide your window is.

   .045 IS NOT ARBITRARY. It is the grain layer's own opacity, further down
   this file and taken from the v2 spec. This is the second thing on the gate
   whose whole job is to be felt and not seen, so it uses the value the first
   one already proved.

   ONE TOKEN, THREE SURFACES. The gradients and their alphas now live in
   --sheet at the top of this file, because the wall and the study need the
   identical sheet and both paint an opaque ground that would hide anything
   layered behind them. Editing the look means editing one place.
   -------------------------------------------------------------------------- */
.nk-board{
  position:absolute;
  inset:0;
  z-index:0;
  pointer-events:none;
  background-image:var(--sheet);
}

/* --------------------------------------------------------------------------
   THE SHEET, OVER IMAGERY.  Jon, 2026-08-15: "In the armory, the grid needs to
   overlay the images too."

   The ground sheet is behind the plates by construction, so the grid stops
   dead at the first strip and the wall reads as a picture sitting ON a drawing
   rather than as a drawing that includes the picture.

   IT CANNOT BE THE SAME ALPHAS. --sheet is tuned against a near-black ground:
   .0105 and .017 of white, which peak at 17 of 255 over the void and are
   invisible the moment there is a lit woodblock underneath. This is the same
   ink at a strength that survives being over a picture, measured in
   scripts/nk_wall_grid.py rather than guessed.

   MUCH SOFTER, Jon 2026-08-15: "the grid over the images in the armory need to
   be reduced in harshness...they are way too harsh."

   My first pass ran .055 and .09, which measured a peak of 33 levels over a
   backdrop reaching 173. I had aimed at "survives being over a picture" and
   overshot into "sits on top of one". The floor I was defending was a delta of
   3, and I shipped eleven times it.

   Both alphas drop by about two thirds. The minor is deliberately taken far
   enough down that it nearly dissolves over a lit plate, exactly as the minor
   does over the void, so what carries across the pictures is the MAJOR rule
   with the fine grid only implied. That is the same relationship Jon approved
   on the ground sheet, applied over imagery instead of over black.
   -------------------------------------------------------------------------- */
:root{
  --sheet-over-minor:rgba(246,244,242,.018);
  --sheet-over-major:rgba(246,244,242,.032);
  --sheet-over:
    repeating-linear-gradient(to right,  var(--sheet-over-major) 0 1px, transparent 1px 120px),
    repeating-linear-gradient(to bottom, var(--sheet-over-major) 0 1px, transparent 1px 120px),
    repeating-linear-gradient(to right,  var(--sheet-over-minor) 0 1px, transparent 1px 20px),
    repeating-linear-gradient(to bottom, var(--sheet-over-minor) 0 1px, transparent 1px 20px);
}

/* --------------------------------------------------------------------------
   BEAMS ON THE GRID.  Jon, 2026-08-15: "very feint beams of light that ride the
   grid lines (randomize which lines) and keep this going non-stop."

   Geometry, colour and motion all live in site/beams.js, which writes them
   inline per travel because every one of them is re-randomised each time. What
   belongs here is only the part that never varies: where the layer sits.

   z-index -1 IS THE DESIGN, not a nudge to get it out of the way. A negative
   z-index child paints above its parent's own background and below its in-flow
   content, which is exactly the sandwich this needs: light moves THROUGH the
   drawing, and can never cross the type. z-index 0 would have put it in the
   positioned-descendant layer, over the words.

   overflow hidden because a beam starts a full length off the edge and ends a
   full length past the far one, and the grounds are the whole viewport.
   -------------------------------------------------------------------------- */
.nk-beams{
  position:absolute;
  inset:0;
  z-index:-1;
  overflow:hidden;
  pointer-events:none;
}

/* --------------------------------------------------------------------------
   THE ABERRATION LAYER.  site/aberrate.js draws a transparent ghost of a
   letter's own line on top of it, so only the red/cyan fringe lands and the
   real glyph is never touched.

   ABOVE EVERYTHING, because the ghost has to sit over whatever it is
   aberrating and the study's ground is z-index 950 with a drill-in at 20 above
   that. Nothing here can be clicked or selected: the letter underneath is
   still the real one and still takes the pointer.
   -------------------------------------------------------------------------- */
.nk-aberr{
  position:fixed;
  inset:0;
  z-index:2147483000;
  pointer-events:none;
  overflow:hidden;
  user-select:none;
}

.nk-beam{
  position:absolute;
  /* the compositor gets told once, rather than promoting on first frame and
     dropping a beam's opening moment while it uploads a layer */
  will-change:transform;
}

/* THE PREMISE. The only real sentence on the gate, so it is the only thing set
   in sentence case: everything else here is a label and caps would make this a
   fourth headline nobody reads. --muted on --void measures 6.4:1, comfortably
   past the 4.5:1 body floor, which is why this is NOT --ink-dim. ink-dim is the
   linework stratum and it does not carry copy anyone is expected to finish.

   Width is capped in ch rather than px so the measure holds at every type size
   the fit solver lands on. Two lines, each its own block, so the break is
   authored rather than left to the viewport. */
.premise{
  margin-top:clamp(18px,2.4vh,34px);
  font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:var(--t-10);
  line-height:1.85;
  letter-spacing:.04em;
  color:var(--muted);
  /* LEFT, and flex-start, because .lockup centres its children and the mark is
     the widest of them. Centred text under a left-set wordmark reads as a
     different element that happens to be nearby. Measured: line 1 started 132px
     right of the wordmark's N and line 2 started 37px right of it, so the block
     had no edge at all. Both now hang off the same vertical as the tagline. */
  align-self:flex-start;
  text-align:left;
  /* The lines are AUTHORED, one statement each, so the measure exists to STOP
     wrapping rather than to shape it. 62ch clipped the first draft at 63 chars
     and text-wrap:balance then split it into a ragged pair, which is balance
     doing its job on a break that should never have existed. 76ch then clipped
     the reviewed copy at 80 chars. 88ch clears the longest authored line with
     headroom, and still sits far inside the wordmark's own width, so the lockup
     keeps its shape. If a line ever needs more than this, shorten the line. */
  max-width:88ch;
  opacity:0;                        /* anime brings it in after the tagline */
}
.premise span{ display:block }
.premise span+span{ color:var(--ink) }   /* line 2 is the claim that lands */

@media (max-width:640px){
  .premise{ font-size:var(--t-08); max-width:38ch; line-height:1.7 }
}

.mark{
  font-family:'Mars','Noto Sans JP','Yu Gothic UI',sans-serif;
  font-size:var(--mark-cap);    /* placeholder; solved at runtime */
  line-height:.95;
  letter-spacing:-.015em;
  text-transform:uppercase;
  white-space:nowrap;           /* one word, one line, never wraps */
  user-select:none;
  margin:0;
  opacity:0;
  text-align:center;
  will-change:opacity,text-shadow;
}

/* CLASS COLLISION, MEASURED 2026-08-17 VIA CDP getMatchedStylesForNode.
 *
 * room.css:1146 styles a DIFFERENT component that happens to share this name:
 * the room boot seal's lockup, a kanji+Latin pair deliberately tracked wide at
 * .24em. The front door's boot wordmark is not that lockup, it is THE wordmark,
 * and boot.js hands it both classes: <h1 class="mark nk-boot-mark">.
 *
 * Both selectors are one class, so specificity ties and source order decides.
 * room.css is later, so .24em beat the authored -.015em and the boot wordmark
 * painted 1300.3px of ink inside a box the solver had locked to 1044px. That
 * 257px of overflow is the whole defect: for ~300ms across the boot -> React
 * handoff the wordmark rendered visibly wider, then React's copy replaced it at
 * the authored tracking and it snapped back.
 *
 * Jon, 2026-08-17: "it gets bigger and then rests at it's smaller variant
 * before cycling. It needs to stay at the smaller variant."
 *
 * Two classes beat one, so this holds no matter which sheet loads first. It
 * neutralises font-size as well: the solver sets that inline, but only after
 * its first pass, and until then --t-12 would leak in the same way.
 *
 * The real repair is a rename, and it belongs to whoever owns .nk-boot-* in
 * room.css. This is the safe half: the front door stops inheriting a lockup it
 * was never meant to wear. */
.mark.nk-boot-mark{
  font-size:var(--mark-cap);
  letter-spacing:-.015em;
}

.tag{
  font-family:'JetBrains Mono',ui-monospace,monospace;
  font-weight:400;
  font-size:var(--tag-cap);     /* placeholder; solved at runtime */
  line-height:1.35;
  letter-spacing:var(--tag-track);
  word-spacing:var(--tag-word);
  text-transform:uppercase;
  color:var(--muted);
  white-space:nowrap;
  user-select:none;
  margin:var(--tag-gap) 0 0;
  /* tracking adds trailing space after the final glyph, which pushes an
     optically centred line right. Pull it back by one step. */
  text-indent:var(--tag-track);
}

/* Words arrive independently, so the line stays visible and each word carries
   its own opacity. Left inline (not inline-block) so the real space characters
   between them still receive word-spacing. */
.tag .w{opacity:0;will-change:opacity}

/* ==========================================================================
   THE BOOT LAYER  ·  see site/boot.js for why it exists.

   Nothing painted until 1470ms because in-browser Babel must fully arrive
   before it will even request the JSX. This layer puts the wordmark on screen
   at ~640ms, in the position it will keep, with a real readout in the
   tagline's slot until the payload is down.

   It borrows .mark rather than restating it, so the boot wordmark and the
   React wordmark are the SAME type at the SAME solved size. The only thing
   that differs between the two lockups is what sits underneath, which is why
   app.jsx FLIPs the mark on handoff instead of cutting: the lockup grows
   downward as the tagline, premise and control arrive, and the mark settles
   up into its final seat rather than jumping there.

   z-index 960 clears the shatter canvas (920) and the room (950). It is
   removed from the DOM on handoff, not just hidden, so nothing it owns can
   intercept a pointer afterwards. That failure mode has already cost this
   project a round: room.jsx:15-28 and the one-shot wall bug of 2026-08-13.
   ========================================================================== */
/* Nothing about composition lives here. boot.js renders a real
   `.gate.comp-N.soft > .lockup` inside this box, so every alignment rule below
   in the COMPOSITION section applies to it unchanged. This element is only the
   fixed, opaque frame that box sits in. */
.nk-boot{
  position:fixed;
  inset:0;
  z-index:960;
  /* THE DRAFTING SHEET IS ON EVERY VIEW, AND THIS ONE WAS THE EXCEPTION.
     Jon, 2026-08-15, on the sheet: "Have them on every page." The countdown
     screen paints an opaque --void ground, which covered the body's copy of it
     the same way the wall and the study do, and those two already answer this
     by applying the token themselves. This is the third surface doing it, from
     the same one definition, so there is still nothing to drift. */
  background-color:var(--void);
  background-image:var(--sheet);
}

/* ==========================================================================
   THE COUNTDOWN.  Jon, 2026-08-17: "I want numbers that count down. the number
   will be large and in the very center of the page until the page is ready to
   load, and then it will load into what we have for the front door...You will
   do away with the small font 'loading' and it's associated countdown...No
   'loading' words anywhere."

   So the boot layer stopped standing in for the front door. It is its own
   screen now, with exactly two things on it: this number, dead centre, and the
   register at the bottom left.

   THE LOCKUP IS STILL IN THE DOM ABOVE, AND IT IS A RULER, NOT A PICTURE.
   boot.js has to solve the enter control's size against the REAL composition
   before React's first render (without that seed the control is a zero-height
   box on frame one and the wordmark lands 57px low, boot.js:173), and a
   measurement rig that differed from the shipping lockup would hand React a
   wrong number. It is hidden by nk.css:643 rather than by a rule of its own:
   dropping `ready` from the boot gate is what the site already means by
   "laid out but not shown", so there is no second hiding mechanism to keep in
   step with the first.

   LEADING ZEROS, so the value is 09 and not 9, matching the shelf markers and
   the anatomy grid. Jon ruled it on 2026-08-17 for consistency across the site,
   and it costs this element nothing while buying it a fixed width: the run no
   longer narrows by a cell at the 10 boundary, so a large centred number holds
   one footprint the whole way down. Nothing here sets tabular figures because
   these are drawn segments on a fixed cell; a 1 already occupies exactly what
   a 7 does.
   ========================================================================== */
.nk-boot-count{
  position:absolute;
  inset:0;
  display:grid;
  place-items:center;
  font-size:var(--nk-count-cap);
  color:var(--ink);
  opacity:0;                 /* anime.js brings it in; see boot.js arrive() */
  user-select:none;
  pointer-events:none;
}

/* THE REGISTER, BOTTOM LEFT, and it is the site's OWN .fineprint rather than a
   second copy of that treatment: same face, same size, same tracking, same
   --ink-dim, same seven-segment year. Exactly two things are restated, and both
   only because this element deliberately sits outside the hidden gate. `left`,
   because .fineprint takes its side from a .comp-N ancestor it no longer has.
   `opacity` is NOT restated: .fineprint ships at 0 for the gate to animate and
   this layer animates its own to the same .80. */
.nk-boot-fine{ left:var(--edge) }

/* ==========================================================================
   SEVEN-SEGMENT NUMERALS. Geometry and behaviour are in seg.js; this is only
   how they sit in a line of type.

   EVERYTHING IS currentColor, deliberately. Each consumer already has a
   ruled colour (.nk-fact-n is --ink-dim, .nk-why-n is --headline,
   .nk-boot-count is --ink) and inheriting it means this treatment spends no
   new token, costs the accent budget nothing, and cannot drift from the
   element it replaced.
   ========================================================================== */
/* SIZE AND GHOST, BOTH MEASURED RATHER THAN CHOSEN.
   deliveries/marks/nk_seg_study.png captures the same number at 1:1 across a
   size ramp and a ghost ramp, enlarged with NEAREST afterwards so the pixel
   grid is the real one. What it settled:

     as first built, 0.98em / ghost .13   the ghost swallows the digit. At
                                          --t-08 you read 88, not 01. Wrong,
                                          and it was live for one run.
     0.98em / ghost .07                   better, still muddy
     1.42em / ghost .13                   segments legible, ghost still wins
     1.42em / ghost .07                   THIS. Reads as a segment display AND
                                          reads as the number.
     1.86em / ghost .10                   unambiguous, but the number then
                                          rivals the kanji beside it and the
                                          head row loses its ranking.

   A display has to show SEVEN bars per digit, so it needs more room than the
   type it replaces. --t-08 resolves to ~9.7px; 1.42em of that is ~13.8px,
   which is the floor this can work at. One variable, because the two
   consumers must not drift into different displays. */
:root{ --nk-seg-h:1.42em; --nk-seg-ghost:.07 }

.nk-seg-svg{
  display:inline-block;
  /* cap height, not line height: the digits have to sit on the same optical
     baseline as the kanji and Latin beside them, and 1em of an SVG box is
     taller than 1em of type. */
  height:var(--nk-seg-h);
  width:auto;
  vertical-align:-.22em;
  overflow:visible;
}
.nk-seg{
  fill:currentColor;
  opacity:var(--nk-seg-ghost);  /* the unlit ghost. seg.js owns the number. */
}
.nk-seg.on{ opacity:1 }
/* a run of segments sitting INSIDE a line of type, as the colophon's year
   does. The wrapper exists so the run can be found and revealed without
   walking text nodes; it adds no box of its own. */
.nk-seg-run{ display:inline; white-space:nowrap }
/* the separator is a stroke, not a filled cell */
.nk-seg-rule{
  fill:none;
  stroke:currentColor;
  stroke-width:2.6;
  stroke-linecap:butt;
}
/* THE REAL NUMBER, STILL THERE. The SVG is aria-hidden, so this carries the
   value for assistive tech and for find-in-page. Not display:none, which
   would take it out of the accessibility tree along with the pixels. */
.nk-seg-sr{
  position:absolute;
  width:1px;height:1px;
  margin:-1px;padding:0;border:0;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
}
@media (prefers-reduced-motion:reduce){
  /* no self-test, and seg.js already returns the settled state; this is the
     belt to that braces, so a number is never left at 88 by a missed frame. */
  .nk-seg{ transition:none }
}

@media (max-width:640px){
  .nk-boot-status{ font-size:var(--t-08); gap:1.1em }
}

/* ==========================================================================
   THE ENTRY CONTROL — the thermal mark.

   In flow, directly under the tagline. Sized from the BRAND width in useFit,
   not from the viewport, so the lockup's proportions hold everywhere.

   Gate spec: this is the only living thing on the page and it carries the only
   saturated pixels. The thermal ramp IS those pixels, so the control spends the
   accent budget once and needs no additional chrome. No pill, no border, no
   hover lift. Register per the standing frame: a control on a machine.
   ========================================================================== */
.enter{
  margin:var(--enter-gap) 0 0;
  padding:0;border:0;background:none;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:0;
  cursor:pointer;
  opacity:0;                          /* arrives last */
  -webkit-tap-highlight-color:transparent;
  transition:filter var(--d-ui) var(--ease-ui),
             transform var(--d-ui) var(--ease-ui);
  filter:brightness(.86) saturate(.92);   /* idles slightly cool */
}
.enter-mark{ display:block;line-height:0 }
.enter-mark svg{ width:100%;height:100%;display:block }

/* The label. RANKING RULED BY KUSANAGI 2026-08-12, overturning my build.

   抜刀 is PRIMARY, in `headline`, at the larger step. The Latin is the
   SIGNATURE beneath it, at --t-10 in `ink_dim`. I had this inverted, and my
   own comment said so out loud ("gloss weight: subordinate to the romaji"),
   which falsified the gate spec's stated hook: the only affordance on screen
   is a Japanese word most visitors cannot read.

   THE GLOSS WORD, decided separately and after two rejections:
     BATTO      rejected (fiber). A transliteration teaches an English reader
                how to SAY a word they still do not understand.
     DRAW       rejected (Jon). Kusanagi had flagged three collisions on the
                bare word: the gunslinger, the tied match, and THE SKETCH.
                The sketch reading is the acute one here because the art
                direction runs blueprint linework over the blade, and Jon hit
                it verbatim: "Draw what? A picture?"
     UNSHEATHE  rejected (Jon). Unambiguous but too formal for the register.
     ENTER      current, and ruled by Jon. Dead as a word, which is exactly
                why it works HERE: the kanji above it carries all the register,
                so the Latin line only has to do one job, say what the button
                does, and never be misread. Any word with flavour competes with
                抜刀 instead of glossing it.

   Romaji lives in exactly one place, the accessible name on the button. That
   is the correct home for pronunciation.

   The kanji STEP below is provisional and is verve's call, not mine. The gate
   spec's 38.4px was set when 抜刀 was the entire control; the brain mark now
   carries the visual weight and this is a caption. Kusanagi's ruling binds
   only the RANKING: kanji outranks Latin in both size and value. */
.enter-primary{
  margin-top:14px;
  font-family:'Noto Serif JP','Yu Mincho',serif;
  font-size:var(--t-18);             /* provisional; verve owns the exact step */
  line-height:1;
  letter-spacing:.30em;
  text-indent:.30em;                 /* tracking pushes right; pull it back */
  color:var(--headline);
  opacity:0;
}
.enter-signature{
  margin-top:10px;
  font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:var(--t-10);
  letter-spacing:.42em;
  text-indent:.42em;
  color:var(--ink-dim);              /* linework/gloss weight, never body copy */
  opacity:0;
}

/* Hover takes it hot. That is the whole affordance: the instrument comes up
   to temperature under your cursor. */
.enter:hover,
.enter:focus-visible{ filter:brightness(1.14) saturate(1.10) }
.enter:focus-visible{ outline:1px solid rgba(246,244,242,.40);outline-offset:12px }
.enter:active{ transform:scale(.985) }

/* Nothing has laid out yet: hide the flash of unsolved type. */
.gate:not(.ready) .lockup{visibility:hidden}

@media (prefers-reduced-motion:reduce){
  .mark,.tag .w{opacity:1!important}
  .mark{text-shadow:none!important}
}

/* ==========================================================================
   ATMOSPHERE
   One fixed, non-interactive layer. The 草薙 rail and the local-time readout
   both lived here and were CUT by Jon 2026-08-12. Noted rather than silently
   dropped: the rail is still in the v2 art-direction spec, so that spec and
   this build now disagree and the spec is the one that is stale.
   ========================================================================== */

/* GRAIN. Straight from the v2 art-direction spec, which I dropped during the
   React rewrite. This is the highest-value item on the page for the Akira /
   Ghost register, and it is not a filter: it is film stock. Without it the void
   is DIGITAL black. With it the void has air.

   steps(1) is load-bearing. A smooth transform makes the grain crawl, which
   reads as a shader. Stepping it makes each position a held frame, which is
   how real film grain behaves. This is the one place CSS keyframes are used
   instead of anime.js, because the spec states it as CSS. */
.grain{
  position:fixed;inset:-50%;
  pointer-events:none;
  z-index:900;
  opacity:.045;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  will-change:transform;
  transform:translateZ(0);
  animation:nk-grain 2000ms steps(1) infinite;
}
@keyframes nk-grain{
  0%  {transform:translate3d(0,0,0)}
  20% {transform:translate3d(-2%,1%,0)}
  40% {transform:translate3d(1%,-2%,0)}
  60% {transform:translate3d(-1%,-1%,0)}
  80% {transform:translate3d(2%,1%,0)}
  100%{transform:translate3d(0,0,0)}
}
/* DEV TOGGLE. /site/?grain=crawl swaps steps(1) for linear so the difference
   is visible on the real page at the real 0.045, and ?grain=off removes it.
   The shorthand above sets steps(1); this longhand overrides just the timing. */
.grain.is-crawl{ animation-timing-function:linear }
@media (max-width:479px){ .grain{opacity:.0225} }



@media (prefers-reduced-motion:reduce){
  .grain{animation:none}
}

/* ==========================================================================
   COMPOSITION.  ?comp=center | a | b | c

   Ruled by Kusanagi 2026-08-12: the centred, evenly-margined lockup is WESTERN
   luxury grammar, not Japanese layout. The correct term is 余白 (yohaku), and
   the principle is 不均斉 (fukinsei), asymmetry. Yohaku is essentially never
   centred, because blank space only reads as charged when there is a mass to
   measure it against. Even margins on four sides give the whitespace no
   direction, and undirected whitespace is just room.

   Also ruled: the fine print goes in ONE corner. Two corners re-centres the
   page, which defeats the entire move.

   GLITCH: Kusanagi ruled against it on the grounds that Akira and the 1995
   Ghost film are hand-drawn cel work whose digital feeling comes from density,
   not signal degradation. OVERRULED BY JON 2026-08-12: cyberpunk is the parent
   genre and those two films are eras within it, not the definition of it.
   Recorded here so the disagreement is visible rather than lost.
   ========================================================================== */
.gate.comp-a, .gate.comp-b, .gate.comp-c{ place-items:center start }
.gate.comp-c{ place-items:center end }
/* mass pushed off the vertical centre too; dead-centre in both axes is the
   same tell in the other direction */
.gate.comp-b{ padding-bottom:max(12vh, var(--nk-gate-pad, 0px)) }  /* the lane still wins when it is larger */
.comp-a .lockup, .comp-b .lockup{ align-items:flex-start }
.comp-c .lockup{ align-items:flex-end }
/* .enter is NOT re-aligned per composition, deliberately. The composition moves
   the BUTTON; it must not re-align the button's CONTENTS. 抜刀 and ENTER are
   labels belonging to the mark above them, so they stay centred on the mark at
   every composition. Aligning them to the page edge instead was the bug: it
   parented the labels to the layout rather than to the thing they name. */
/* the optical-centring indent is only correct for a centred line */
.comp-a .tag, .comp-b .tag{ text-align:left;  text-indent:0 }
.comp-c .tag{ text-align:right; text-indent:0 }
/* the boot readout occupies the tagline's slot, so it follows the tagline's
   composition rule. The indent exists to optically re-centre a tracked line and
   is wrong the moment the line is edge-set. */
.comp-a .nk-boot-status, .comp-b .nk-boot-status{ left:0; right:auto }
.comp-c .nk-boot-status{ left:auto; right:0; flex-direction:row-reverse }

/* THE FINE-PRINT STRATUM.
   Every reference in the frame runs TWO registers at once: the monumental one
   and a dense small technical-administrative one. That second stratum is what
   makes the first read as situated in a world instead of floating in a
   portfolio. Content is real: the specced register line, plus 草薙 returned at
   its CORRECT RANK. Kusanagi is the parent system, not the product, so it is
   an attribution, and attributions do not get full-height edge elements. */
.fineprint{
  position:fixed;
  bottom:var(--edge);
  font-family:'JetBrains Mono',ui-monospace,monospace;
  font-size:var(--t-08);
  /* tighter leading with the smaller size. Loose leading on small type reads as
     sparse, and DENSITY is the entire job of this stratum. */
  line-height:1.75;
  letter-spacing:.16em;
  color:var(--ink-dim);
  opacity:0;
  pointer-events:none;
  user-select:none;
  z-index:40;
}
.fineprint .jp{ font-family:'Noto Serif JP','Yu Mincho',serif;letter-spacing:.10em }
/* THE REGISTER LIFTS OFF THE DEBRIS. Jon, 2026-08-24: "on the study page,
   you need to move the copyright up so that the soundcloud isn't covered up
   by the binary that fell to the floor." The shatter's floor is 28px above
   the bottom edge (shatter.js T.floorInset) and the settled glyphs stand on
   it: measured 2026-08-24 at 1600x1000, the band's top is 41px off the foot
   in the register's column, while the music control seated under the
   register ran 58..78px off the foot, so its lower half sat in the tumbling
   pieces. 36px more lifts the control's foot to 94px, well clear of the
   band with air for glyphs that bounce. The gate carries .is-shattered from
   the moment the door is pressed; the register's ResizeObserver reads its
   bottom off the element, so the fine lane follows automatically. */
.gate.is-shattered .fineprint{
  /* the 96px floor only bites on phones, where --edge is 16px: measured at
     390x844 the +36 alone put the register 52px off the foot and the control
     under it at 26..46, inside the band (top at 45). 96 seats the control at
     70..90, 25px clear. At the 84px desktop edge, 120 wins as before. */
  bottom:max(calc(var(--edge) + 36px), 96px);
  transition:bottom 640ms cubic-bezier(.2,.7,.2,1);
}
.comp-a .fineprint{ left:var(--edge) }
.comp-b .fineprint{ right:var(--edge);text-align:right }
.comp-c .fineprint{ left:var(--edge) }
/* ==========================================================================
   THE DOOR ON A PHONE IS CENTRED.  Jon, 2026-08-24: "when I'm in mobile view,
   the brain icon needs to be centered. The text just above it needs to be
   centered and balanced. And then the copyright needs to be centered as well
   at the bottom."

   The compositions above set the lockup's children to the wordmark's left
   vertical, and the reasoning ("centred text under a left-set wordmark reads
   as a different element") holds on a desktop where the wordmark is a
   left-set object with field to its right. Measured at 360x800 the wordmark
   spans the whole width (16 to 344), so there is no left vertical to hang
   from and the edge-set premise and 36px brain simply read as fallen to one
   side. One axis, one centre: the tagline, the premise (balanced, so its
   two lines break evenly), the control, and the register, which was
   display:none here and is now the same three lines centred on the foot.
   ========================================================================== */
@media (max-width:767px){
  .gate.comp-a, .gate.comp-b, .gate.comp-c{ place-items:center }
  .comp-a .lockup, .comp-b .lockup, .comp-c .lockup{ align-items:center }
  .premise{ align-self:center; text-align:center; text-wrap:balance; max-width:34ch }
  .comp-a .tag, .comp-b .tag, .comp-c .tag{ text-align:center; text-indent:var(--tag-track) }
  .fineprint, .comp-a .fineprint, .comp-b .fineprint, .comp-c .fineprint{
    display:block; left:var(--edge); right:var(--edge); text-align:center }
  .nk-boot-fine{ right:var(--edge); text-align:center }
}
@media (prefers-reduced-motion:reduce){ .fineprint{opacity:.55!important} }

/* ==========================================================================
   草薙 THE SIGNAL. The omnipresent music control (site/music.js). Bottom
   centre, the one strip of chrome clear on every surface: the register is
   bottom-left, the exits top-right, the escape bottom-right. Register type,
   hairlines, no fill; it reads as an instrument label, not a media player.
   ========================================================================== */
.nk-music{
  position:fixed;
  /* SEATED BY music.js, directly under whichever register is on screen (the
     gate's .fineprint, the countdown's seal, a room's .nk-wall-seal): it sets
     top and left from the register's live box. Jon, 2026-08-24: "place the
     controller directly under the copyright." No coordinates live here. */
  left:var(--edge); top:auto;
  /* ABOVE THE ROOM, a real now-playing bar. The rooms mount at z-950 and
     their scroll catchers (.nk-facts, .nk-shoin-scroll) fill the viewport, so
     a body-level control under 950 is buried and cannot be pressed (measured:
     .nk-facts intercepted every click at bottom centre). 960 sits over the
     room and its own modals, the persistent-player pattern. */
  z-index:960;
  display:flex;
  align-items:center;
  gap:.8em;
  font-family:'JetBrains Mono',ui-monospace,monospace;
  opacity:0;                        /* arrives only once a track is loaded */
  transition:opacity 640ms linear;
  pointer-events:none;
}
.nk-music.is-centred{ transform:translateX(-50%); }
.nk-music.is-unseated{ opacity:0!important; pointer-events:none!important; }
.nk-music.is-on{ opacity:.9; pointer-events:auto; }
/* muted: still there to press, just dim, so the way back is never hidden */
.nk-music:not(.is-on){ opacity:.42; pointer-events:auto; }
/* a video has the floor: the bar leaves entirely so it never sits on the
   player's own controls, and returns when the clip is closed */
.nk-music.is-ducked{ opacity:0!important; pointer-events:none!important; }
.nk-music-btn{
  display:flex; align-items:center; gap:.7em;
  margin:0; padding:.2em 0; border:0; background:none;
  color:var(--ink); cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.nk-music-meta{
  display:flex; align-items:baseline; gap:.55em;
  /* the register's own size and tracking, so the line reads as its fourth row */
  font-size:var(--t-08); letter-spacing:.16em; text-transform:uppercase;
  white-space:nowrap; max-width:44vw; overflow:hidden; text-overflow:ellipsis;
}
.nk-music-artist{ color:var(--accent-red); }
.nk-music-track{ color:var(--muted); }
.nk-music:not(.is-on) .nk-music-artist{ color:var(--muted); }
.nk-music-btn:hover .nk-music-track{ color:var(--ink); }

/* THE GLYPH. 音 (sound) is the toggle's mark, in the site's kanji face, set
   the way every kanji + roman lockup on the site is set: accent when the
   sound is on, muted when it is off. */
.nk-music-k{
  font-family:'Noto Serif JP','Yu Mincho',serif;
  font-size:calc(var(--t-08) * 1.45); line-height:1;
  color:var(--accent-red);
  transition:color 240ms linear;
}
.nk-music:not(.is-on) .nk-music-k{ color:var(--muted); }

/* THE STRIP. Jon, 2026-08-24: "I want the EQ to span the entire top of the
   page (shallow)." One canvas, edge to edge, 12px deep on desktop, drawn by
   music.js from either a live analyser or its synthetic signal. It sits
   above the header lane (the room title and the exits start at ~44px) so
   nothing under it is ever covered, and it takes no pointer events. */
.nk-music-strip{
  position:fixed; left:0; top:0;
  width:100%; height:12px;
  z-index:961;
  display:block;
  opacity:0;
  transition:opacity 640ms linear;
  pointer-events:none;
}
.nk-music-strip.is-live{ opacity:.85; }
@media (max-width:640px){ .nk-music-strip{ height:9px; } }
@media (prefers-reduced-motion:reduce){ .nk-music-strip{ display:none; } }

/* THE ATTRIBUTION, the legal half: the SoundCloud mark linking to the track's
   own page. Dim by default, brightens on hover like every other exit. */
.nk-music-sc{
  display:inline-flex; align-items:center;
  color:var(--ink-dim); opacity:.7;
  transition:color 240ms linear, opacity 240ms linear;
}
.nk-music-sc:hover{ color:var(--muted); opacity:1; }
.nk-music-sc svg{ display:block; }

/* THE PLAYER ITSELF stays out of sight; the attribution above is what the
   terms ask for. 1px, off in the corner, present so it can stream. */
.nk-music-frame{
  position:fixed; left:-9999px; bottom:0;
  width:1px; height:1px; border:0; opacity:0; pointer-events:none;
}
@media (max-width:640px){
  .nk-music-meta{ max-width:62vw; }
}

/* ==========================================================================
   HALATION  ("soft anime").  ?soft=off to compare.

   The mechanism is HALATION, not blur. Anime on film blooms light OUTWARD from
   bright areas into the dark around them while the EDGES STAY SHARP. Blurring
   the frame would just look out of focus, which is the opposite read.

   WHY PER-ELEMENT AND NOT ONE FULL-VIEWPORT LAYER.
   A single fixed overlay doing backdrop-filter + mix-blend-mode:screen models
   this exactly, and that is what the PIL study rendered. Two reasons it is not
   what shipped:
     1. UNIFORM BLOOM SMEARS THE SMALL TYPE. Visible in the study: at amount
        0.85 the tagline starts to mush, and the 8px fine print never wants any
        bloom at all. Per-element lets the wordmark glow hard and leaves the
        administrative stratum alone, which is the correct answer anyway.
     2. This page is four light objects on void. There is nothing else TO bloom,
        so a full-viewport GPU composite buys nothing over four filters.

   drop-shadow and NOT text-shadow on .mark, deliberately: useScramble writes
   el.style.textShadow every frame for the chromatic aberration, so a
   text-shadow glow there would be wiped 60 times a second. filter is a
   separate channel and the two compose.

   Radii are in em so they track the RUNTIME-SOLVED font size. A px radius
   would be correct at 1440 and wrong at every other width.
   ========================================================================== */
.soft .mark{
  filter:drop-shadow(0 0 .26em rgba(246,244,242,.38))
         drop-shadow(0 0 .08em rgba(246,244,242,.30));
}
.soft .tag{                         /* much less: this is 18px, not 84px */
  filter:drop-shadow(0 0 .30em rgba(147,144,142,.28));
}
/* the mark blooms WARM, matching the thermal it is already spending. A white
   halo here would desaturate the one saturated object on the page. */
.soft .enter-mark{
  filter:drop-shadow(0 0 .05em rgba(255,140,70,.42));
}
.soft .enter-primary{
  filter:drop-shadow(0 0 .34em rgba(246,244,242,.30));
}
/* .enter-signature and .fineprint get NO bloom. Both are small and dim; glow
   on either costs legibility we already measured as tight. */

/* NO VIGNETTE, NO LIFTED BLACKS. Both were built, both were cut by Jon
   2026-08-12: ".55 bloom only". Recorded because they are the obvious things to
   reach for next time and they were already tried:
     LIFT 16  turned the void to grey mud. Washed out is not soft.
     LIFT 5   defensible but unnecessary once the bloom is doing the work.
     VIGNETTE put depth back that the bloom flattened, at the cost of a darkened
              frame edge the page never asked for.
   Halation alone. Nothing global. */

/* ==========================================================================
   THE SHATTER.  Canvas overlay + the one-frame handoff.
   Engine lives in site/shatter.js (plain JS, outside the Babel transform).
   ========================================================================== */
.shatter-canvas{
  position:fixed;inset:0;
  pointer-events:none;
  z-index:920;              /* above grain (900), below nothing */
  opacity:0;
}

/* The handoff. shatter.js adds .shattered to the gate in the SAME frame the
   canvas takes its first draw, so the DOM disappears exactly as the particles
   appear at the identical coordinates. visibility, not display: display would
   reflow the document and the colophon would jump. */
.gate.shattered .mark,
.gate.shattered .tag,
.gate.shattered .premise,
.gate.shattered .enter{ visibility:hidden }
.gate.shattered{ cursor:default }
/* THE WORDMARK BEHIND A MODAL.  Jon, 2026-08-24: "I like the NEURALKATANA
   logo showing in the background in the study, only when a modal is
   displayed. The logo gets blurred but is still legible and looks a bit
   cool. As soon as the modal is closed that logo is not there at all."

   He saw it by accident: a React className rewrite had wiped shatter.js's
   .shattered and the whole door was standing on top of the study; the
   modal's backdrop blur (7px on .nk-why) turned the wordmark into that
   ghost. The accident is fixed above; this keeps the one part of it he
   liked, on purpose. room.jsx puts nk-why-open on <html> while the study's
   panel is open and not closing. The real wordmark comes back at half
   strength in its own seat under the panel, where the panel's blur does the
   rest, and leaves the instant the panel does. */
.gate.shattered .mark{
  /* !important on purpose: the intro animates .mark's opacity through an
     INLINE style (anime.js), which outranks any stylesheet rule and pinned
     the ghost at full strength (measured 2026-08-24: opacity 1 in every
     state). Only an important declaration beats an inline one. */
  opacity:0!important;
  transition:opacity 480ms cubic-bezier(.2,.7,.2,1), visibility 0s linear 480ms;
}
html.nk-why-open .gate.shattered .mark{
  visibility:visible;
  opacity:.6!important;
  transition:opacity 480ms cubic-bezier(.2,.7,.2,1), visibility 0s linear 0s;
}
/* .premise joined this list on Jon's ruling, 2026-08-14: it was the last
   ceremonial element still standing in 書院, and only by omission, because it
   was added to the gate after the harvest list was written. It shatters now,
   so it must be hidden in the same frame as everything else it shatters with.
   Miss this half and the sentence sits intact on top of its own debris.

   .fineprint still survives on purpose. See the note in shatter.js. */

/* ==========================================================================
   THE REVEAL.  Two lines, after the field settles.

       THE INSTRUMENT IS THE SHELL
       THE OPERATOR IS THE GHOST

   These are TITLE CARDS, not UI. Three decisions worth stating, because each
   one had an obvious alternative I turned down:

   1. THEY OCCUPY THE VOID THE SHATTER MADE. The debris is at the foot of the
      screen; everything above it is empty for the first time since load. So the
      block is centred in the upper 74vh, not the full viewport, and the pieces
      stay visible underneath. The shatter cleared the stage FOR this.

   2. NO MOVEMENT. Film titles do not slide, drift or rise. They are simply
      there and then they are not. A translate would make them feel like UI
      arriving; stillness makes them feel like a statement being made.

   3. THEY RESOLVE OUT OF ABERRATION. Each line fades up with a red/cyan split
      that COLLAPSES TO ZERO as it settles. That is the one aberration grammar
      this project has already ruled legitimate: a transient of resolution,
      earned because it resolves, as opposed to permanent decorative glitch.
      The line is not glitching, it is coming INTO FOCUS.

   The gap between the two is the whole effect. Line one is a statement about an
   object. Line two is about the person reading it, and it only lands if line
   one has been allowed to finish being true first.
   ========================================================================== */
.reveal{
  position:fixed;
  left:0; right:0; top:0;
  height:72svh; height:72vh;
  display:flex;
  flex-direction:column;
  align-items:center;              /* CENTRED BLOCK. not left. */
  justify-content:center;
  pointer-events:none;
  user-select:none;
  z-index:930;
}
@supports (height:72svh){ .reveal{height:72svh} }

/* MARS, not the mono. The face that just shattered comes back to speak.
   Size is NOT set here: each line is solved at runtime to span the SAME width,
   so the two form a justified block. See fitReveal() in app.jsx. */
.reveal-line{
  font-family:'Mars','Noto Sans JP',sans-serif;
  font-size:48px;                  /* placeholder; solved at runtime */
  letter-spacing:.02em;
  text-indent:.02em;               /* tracking pushes right; pull it back */
  line-height:1.06;                /* tight: the two lines are ONE mass */
  color:var(--headline);
  white-space:nowrap;
  opacity:0;
  margin:0;
  will-change:opacity,text-shadow;
}
.reveal-line + .reveal-line{ margin-top:.10em }
.soft .reveal-line{ filter:drop-shadow(0 0 .22em rgba(246,244,242,.30)) }
@media (prefers-reduced-motion:reduce){ .reveal-line{text-shadow:none!important} }

/* ==========================================================================
   THE SEAL ABOVE THE SIGNATURE.  Jon, 2026-08-23: the binary brain "needs to
   be present on pretty much every page... consistent... tasteful the way the
   Japanese would have done it." It rides the register block every page
   carries (.fineprint on the gate, .nk-wall-seal in the rooms, .nk-boot-seal
   over the film and the lore): once, in the same corner, above the copyright
   the way a hanko sits above a signature. 64px is the brand README's own
   floor for the binary tier, below which the inscription reads as texture.
   Slightly under full strength so it sits WITH the register's ink-dim lines
   rather than shouting over them. */
/* 64px, THE BINARY TIER, and this is the size Jon approved.  A one-third cut
   was tried on 2026-08-23 and sent back ("now it's way too small"); 64 is also
   the brand README's floor for the binary inscription. The seal is NOT on the
   front door (its brain is the door) and NOT on the study (its brain is the
   scroll cue), so it appears where the register is the room's own: the rack,
   the videos, the armoury, and the two framed pages. */
.nk-seal-mark {
  display: block;
  width: 64px;
  height: 64px;
  margin: 0 0 10px;
  opacity: .88;
  pointer-events: none;
  user-select: none;
}
