/* N5HQ design loop — piece 2 "Instruments"
   Tokens only: carbon / bone / vein / glow. Weight 400 everywhere.
   Faces: Doto 400, fallback Jersey 10 (display/wordmark), Inter (body/heading),
   JetBrains Mono (label).
   Spacing: 4 / 12 / 64. No shadows, no borders except the 1px vein divider
   the caller may add around this component (not added here).

   ---------- fluid edge-draft scaling (2026-09-08/09) ----------
   Two anchors: 1440x900 (today's fixed-px design, must stay byte-identical)
   and 2504px wide (Eric's 2K-monitor spec: corners 100px/60px in, lockup
   ~300px, everything else 1.35x). Ramp is linear in `100vw - 1440px`, which
   is exactly 0 at the low anchor, so every clamp() pins bit-exact at 1440
   regardless of the slope's rounding — see design-system.md "Fluid scaling"
   for the full derivation. Four tokens, expected to be re-declared (byte-
   identical) in every page's own :root, since this file is meant to be
   linked or ported standalone:
     --n5i-inset-x / --n5i-inset-y  corner padding, from the viewport edge
     --n5i-lockup-w                 wordmark lockup width (drives height via
                                     aspect-ratio; NEVER set height directly)
     --n5i-u                        the "everything else" scale, encoded as
                                     a LENGTH (1px..1.35px) so `calc(N * var(--n5i-u))`
                                     type-checks as number*length=length. This
                                     is the one number that scales type,
                                     spacing, icons, the clock cluster, etc.
   Hairlines (1px borders/rules/dividers) are deliberately NOT scaled. */

.n5i-root,
.n5i-root * { box-sizing: border-box; }

.n5i-root {
  color: var(--bone, #e4dfda);
  font: 400 calc(16 * var(--n5i-u, 1px))/1.25 var(--n5i-sans, 'Inter', system-ui, -apple-system, sans-serif);
}

/* ---------- shared atoms ---------- */

.n5i-cluster {
  display: flex;
  flex-direction: column;
  gap: calc(4 * var(--n5i-u, 1px));
}

.n5i-label {
  font: 400 calc(12 * var(--n5i-u, 1px))/1.25 var(--n5i-mono, 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, Menlo, monospace);
  letter-spacing: -0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}

.n5i-dim { opacity: 0.55; }

.n5i-mark {
  font: 400 calc(40 * var(--n5i-u, 1px))/0.70 var(--n5i-display, 'Doto', 'Jersey 10', system-ui, monospace);
  text-decoration: none;
  color: inherit;
  display: inline-block;
  padding-top: calc(6 * var(--n5i-u, 1px));
}
.n5i-logo-img{height:calc(40 * var(--n5i-u, 1px));width:auto;display:block}
/* Logo lockup: still SVG at rest; the mark itself carries both colour states now (no more
   canvas keying or clip video decode). Width tracks the role label under it at rest
   (min-width is below the label's own max-content width at 1440, so nothing changes there);
   above ~1441px min-width takes over as the corner's shrink-to-fit driver and carries the
   lockup up to --n5i-lockup-w (~300px at 2504) — width stays 100% (never set height). */
.n5i-lockup{position:relative;display:block;width:100%;min-width:var(--n5i-lockup-w, 147px);aspect-ratio:1698/804;height:auto;padding-top:0;line-height:0}
/* .lk-hover-fb (Eric, 2026-09-24): the flashbang-mode hover mark -- same drawing as
   .lk-hover but with the violet fill swapped for the flashbang accent #01bdff and every
   other fill swapped for its f() (invert+hue-rotate180) equivalent. Positioned as a third
   overlay here (theme-agnostic atoms); which one of .lk-hover / .lk-hover-fb is actually
   shown/animated is decided by the flashbang override block in this page's own <style>
   (ninja keeps using .lk-hover; flashbang swaps to .lk-hover-fb and hides .lk-hover
   entirely so it can never receive the animation or fire animationend). */
.n5i-lockup .lk-rest,.n5i-lockup .lk-hover,.n5i-lockup .lk-hover-fb{position:absolute;inset:0;width:100%;height:100%;display:block;object-fit:contain}
.n5i-lockup .lk-hover,.n5i-lockup .lk-hover-fb{opacity:0}
/* neon strike (Eric, 2026-09-24): mouseenter/focus adds .lk-neon, which runs the ~.8s tube-flicker
   keyframes below on the violet hover mark (rest hides underneath); JS swaps .lk-neon -> .lk-hov
   on animationend, which just holds the violet mark lit. mouseleave/blur strip both
   classes synchronously — there is no exit animation, so a re-enter always restarts the strike clean.
   No glow (Eric, 2026-09-24: "remove the glow from the logo animation entirely") -- only
   opacity flickers now, timing unchanged. */
.n5i-lockup.lk-armed.lk-neon .lk-rest,.n5i-lockup.lk-armed.lk-hov .lk-rest{opacity:0}
.n5i-lockup.lk-armed.lk-hov .lk-hover{opacity:1}
.n5i-lockup.lk-armed.lk-neon .lk-hover{animation:n5i-neon-strike .8s steps(1,end) forwards}
@keyframes n5i-neon-strike{
  0%{opacity:0}
  8%{opacity:.9}
  18%{opacity:.2}
  32%{opacity:1}
  46%{opacity:.5}
  60%{opacity:1}
  100%{opacity:1}
}
@media (prefers-reduced-motion:reduce){.n5i-lockup .lk-hover,.n5i-lockup .lk-hover-fb{animation:none}.n5i-lockup .lk-rest{opacity:1!important}.n5i-lockup:hover .lk-hover,.n5i-lockup:focus-visible .lk-hover{opacity:1}.n5i-lockup:hover .lk-rest,.n5i-lockup:focus-visible .lk-rest{opacity:0!important}}


/* ---------- odometer: the shared instrument primitive ---------- */
/* Every rolling number is one or more of these columns. Motion is
   transform-only (translateY via a custom property), duration is a
   custom property so JS can set 900ms on first load and >=400ms
   thereafter. Nothing here moves under reduced motion.

   --n5i-row is a PIXEL length, not an em multiple: JS measures the real
   rendered row height after fonts.ready and snaps it to an integer pixel
   value, so every translateY lands on a whole device pixel. Without this,
   the em-based fractional-pixel offsets subpixel-antialias the glyphs and
   can fringe with colour at 1x DPR. Each column and its strip are also
   promoted to their own composited layer (translateZ(0) + backface-
   visibility:hidden) so that antialiasing stays greyscale, not subpixel. */

.n5i-odo {
  --n5i-row: calc(20 * var(--n5i-u, 1px));     /* fallback row height; JS overwrites with the exact px */
  --n5i-pos: 0;        /* current row index (0-9, or 10-19 mid-carry) */
  --n5i-dur: 500ms;    /* per-tick transition duration, caller may override */
  display: inline-block;
  overflow: hidden;
  height: var(--n5i-row);
  vertical-align: top;
  text-align: center;
  transform: translateZ(0);
  backface-visibility: hidden;
  -webkit-font-smoothing: antialiased;
}

.n5i-odo-strip {
  display: flex;
  flex-direction: column;
  will-change: transform;
  transform: translateY(calc(var(--n5i-row) * -1 * var(--n5i-pos))) translateZ(0);
  transition: transform var(--n5i-dur) cubic-bezier(0.16, 1, 0.3, 1);
  backface-visibility: hidden;
  -webkit-font-smoothing: antialiased;
}

.n5i-odo-strip > span {
  display: block;
  height: var(--n5i-row);
  /* --n5i-lh is set by JS on display (clock) columns only: the line-height
     that puts the digit's ink flush with the row (see snapRow) */
  line-height: var(--n5i-lh, var(--n5i-row));
}

/* fallbacks only — JS (snapRow) overwrites --n5i-row with the exact measured
   px the instant fonts are ready, so these just need to scale for no-JS/
   pre-paint parity, not be perfectly ink-fitted */
.n5i-odo--display { --n5i-row: calc(56 * var(--n5i-u, 1px)); }
.n5i-odo--label   { --n5i-row: calc(15 * var(--n5i-u, 1px)); }

@media (prefers-reduced-motion: reduce) {
  .n5i-odo-strip { transition: none !important; }
}

/* ---------- A. clock ---------- */

@font-face {
  font-family: 'Bigger Display';
  src: url(/assets/bigger-display.otf) format('opentype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

.n5i-clock-disp {
  font: 400 calc(80 * var(--n5i-u, 1px))/0.70 'Bigger Display', var(--n5i-display, 'Doto', 'Jersey 10', system-ui, monospace);
  font-variant-numeric: tabular-nums;
  /* Bigger Display ships one style (weight 900 in its OS/2 table) — a same-
     colour stroke is the only way to go heavier. Paint-only: layout and the
     odometer's ink fitting are untouched (Eric, 2026-09-08). Scaled with
     --n5i-u (2026-09-09) so the stroke stays the same RELATIVE weight
     against the glyph as the digits grow 1.35x, instead of visually
     thinning out at 1px against an 80->108px face. */
  -webkit-text-stroke: calc(1 * var(--n5i-u, 1px)) currentColor;
  white-space: nowrap;
  display: inline-flex;
  /* stretch, not baseline — the AM/PM stack needs the full height of the
     digit row (its own tallest sibling) to pin AM to the top and PM to
     the bottom of it via justify-content:space-between */
  align-items: stretch;
}

/* AM/PM shown together, always — never removed, never re-typeset. The
   inactive one dims rather than disappearing so the stack never reflows
   as the period changes. */
.n5i-ap-stack {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  margin-left: calc(4 * var(--n5i-u, 1px));
}

.n5i-ap-stack span {
  font: 400 calc(30 * var(--n5i-u, 1px))/0.70 'Bigger Display', var(--n5i-display, 'Doto', 'Jersey 10', system-ui, monospace);
  color: var(--bone);
  /* AM/PM go the other way (Eric, 2026-09-08): the face has only its Black
     cut, so a thin stroke in the ground colour erodes the glyph edges and
     reads as a lighter weight. Paint-only. Scaled with --n5i-u, same
     rationale as .n5i-clock-disp above. */
  -webkit-text-stroke: calc(0.6 * var(--n5i-u, 1px)) var(--carbon);
}

/* a 10% ghost of the inactive meridian (Eric, 2026-09-08: "lower the transparency
   further, potentially to 10%") — a third, deliberate exception to "Bone at 100% or
   55%, nothing else"; it is there for layout stability, not to be read */
.n5i-ap-inactive {
  opacity: .1;
}

.n5i-clock-colon { padding: 0 calc(2 * var(--n5i-u, 1px)); animation: n5i-colon-blink 1s steps(1, end) infinite; }

@keyframes n5i-colon-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* ---------- B. telemetry cluster ---------- */

.n5i-tele {
  display: flex;
  flex-direction: column;
  gap: calc(4 * var(--n5i-u, 1px));
  align-items: flex-end;
  text-align: right;
}

.n5i-tele-line {
  font: 400 calc(12 * var(--n5i-u, 1px))/1.25 var(--n5i-mono, 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, Menlo, monospace);
  letter-spacing: -0.05em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.n5i-tele-line b { font-weight: 400; }

/* the state line: one dot in the violet glow, and nothing else on the page */

.n5i-state {
  display: inline-flex;
  align-items: center;
  gap: calc(4 * var(--n5i-u, 1px));
}

.n5i-dot {
  width: calc(4 * var(--n5i-u, 1px));
  height: calc(4 * var(--n5i-u, 1px));
  border-radius: 9999px;
  background: var(--glow, #a493ff);
  display: inline-block;
  flex: 0 0 auto;
  opacity: 1;
}

.n5i-dot--pulse {
  animation: n5i-pulse 1s linear infinite;
}

@keyframes n5i-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.15; }
}

@media (prefers-reduced-motion: reduce) {
  .n5i-dot--pulse { animation: none; opacity: 0.7; }
  .n5i-clock-colon { animation: none; opacity: 1; }
}

/* ---------- C. dimension readout ---------- */
/* An inline footnote at the end of the dimmed "Open to roles..." sentence.
   `position:relative; top:-.45em` is a paint-only offset — it never
   participates in layout, so it can't collide with the line above; if it
   wraps, it wraps as its own inline box with normal line flow underneath.
   It carries no opacity of its own: it lives inside the sentence's own
   .n5i-dim paragraph, so it inherits exactly that one 55% dim — never a
   second, compounded opacity. */

.n5i-dim-readout {
  display: inline-block;
  font: 400 calc(12 * var(--n5i-u, 1px))/1.25 var(--n5i-mono, 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, Menlo, monospace);
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  margin-left: calc(12 * var(--n5i-u, 1px));
  position: relative;
  top: -.45em;
  white-space: nowrap;
  vertical-align: baseline;
}

/* 2026-10-07 UPDATE — tagline is now "DevOps & Network Architecture
   enthusiast ...", so the anchor word is "Architecture" (line 1's last
   word), not "Infrastructure": left is 379u (was 272u), the x where
   "Architecture" begins, measured with real webfonts at 768/1024/1280/1440/
   1920/2504 (same 379u at every width, same reasoning as below). The
   272u / "Infrastructure" text below describes the 2026-09-09 wording and
   is kept for the derivation.
   Moved to sit ON the empty space beside the clock digits, above the word
   "Infrastructure" (Eric, 2026-09-09 — see .n5i-clocksentence below for why
   that word, and only that word, is a stable anchor). Positioned absolute
   against .n5i-clocksentence itself (given position:relative below), so:
     top: 0 — the paragraph's own line-box top is set by the clock (the
       tallest inline content on line 1, vertical-align:baseline), which is
       exactly the top of the AM/PM stack (see .n5i-ap-stack: stretched to
       the clock row's full height, AM first via justify-content:space-
       between) — so top:0 here IS "align the top of the resolution readout
       to the top of AM", by construction, not by measured offset.
     left: 272u — the x-position where the word "Infrastructure" begins on
       line 1, measured with Playwright once the real webfonts (Inter for
       the sentence, "Bigger Display" for the clock) were loaded and
       resolved (document.fonts.ready) — a measurement taken against a
       fallback face gives a different, wrong number. Verified stable
       (270-273u) across 768/1024/1440/1920/2504 because it depends only on
       the fixed words "12:00 AM DevOps & " and the paragraph's own fixed
       font-size, neither of which changes with the container's max-width —
       only where the wrap LANDS depends on that, and .n5i-clocksentence's
       max-width is chosen precisely so the wrap lands right after
       "Infrastructure", not mid-word through "enthusiast".
   Still a right-aligned (align-items:flex-end) ledger column, per Eric's
   2026-09-08 resolution-fps-alignment.png — only the anchor edge moved
   from "sentence's right edge, below" to "Infrastructure's left edge,
   above". Below 767px this is overridden back to static flow (see
   p3/home.html's phone media query) — the corners stack there and
   "Infrastructure" is no longer on the same line as the clock, so there is
   no word to sit on top of. */
.n5i-readouts {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: calc(4 * var(--n5i-u, 1px));
  position: absolute;
  top: 0;
  left: calc(379 * var(--n5i-u, 1px));
}

.n5i-readouts .n5i-dim-readout {
  display: block;
  margin-left: 0;
  position: static;
  top: auto;
}

/* the FPS line reads as a sub-item of the resolution line above it — the
   ↳ glyph carries that now; with the column right-aligned an indent would
   be invisible, so none. Selector kept at two classes to match
   .n5i-readouts .n5i-dim-readout's specificity */
.n5i-readouts .n5i-fps-readout {
  margin-left: 0;
}

/* ---------- D. clock + About: one composed sentence ---------- */
/* label -> ONE paragraph whose first word is the clock. The clock sits
   inline (display:inline-block; vertical-align:baseline) so the sentence
   wraps under and around it exactly like a sentence with a bolded first
   word — not a separate line, not a separate cluster. There is no second
   "About" label — Local time is the only label in this corner. */

.n5i-clocksentence {
  /* position:relative so .n5i-readouts (absolute) can anchor to THIS box —
     its line-box top (set by the clock, the tallest thing on line 1) is
     the AM/PM stack's top, and its left edge is the same left edge the
     272u readout offset below is measured from. Added 2026-09-09,
     see .n5i-readouts above. */
  position: relative;
  /* 2026-10-07 UPDATE — basis 480->590 for the new tagline "DevOps & Network
     Architecture enthusiast who designs, deploys, and maintains real
     infrastructure." At 480 it wrapped to FIVE lines (line 1 "DevOps &
     Network", "Architecture" dropped to line 2). Measured with real
     webfonts: line 1 "DevOps & Network Architecture" ends at ~524u,
     "enthusiast" would need ~650u, line 2 "enthusiast who designs,
     deploys, and maintains" is ~563u, and adding " real" would need
     ~615u. 590 sits in the gap on both sides (27u above line 2, ~25u below
     the next word), so rounding can't flip either wrap, and gives three
     even lines (~524 / ~563 / ~543u) with nothing sticking out. The
     readout (left: 379u, see .n5i-readouts) sits over "Architecture". The
     480 derivation below describes the old wording and is kept for the
     history. Must stay in lockstep with .bl's basis in home/index.html.
     Original (2026-09-09): the cluster's measure, in the same scaled unit as everything else — ch
     was never the binding constraint here, the corner's content box was.
     Basis 587.5->480 (2026-09-09, Eric: "'enthusiast' is sticking out too
     much... try to keep everything at 3 lines, if not 4 is fine... move
     the res/fps counter to sit on top of the word 'Infrastructure'"). At
     587.5 (real webfonts, not the fallback face — see the 272u note on
     .n5i-readouts) line 1 was "DevOps & Infrastructure enthusiast" at
     EVERY desktop width 768-2504: "enthusiast" always ends the line only
     ~26-45u short of the max-width, so it visually reads as the one
     stretched-out line next to two much-shorter ones below it — that is
     the "sticking out". 480 forces the wrap one word earlier: line 1 is
     always "DevOps & Infrastructure" (that word's own right edge sits at
     ~432u, "enthusiast" would need ~561u — 480 sits in the middle of that
     gap so ordinary sub-pixel/DPR rounding at any tested width can't flip
     the wrap either way). Costs a 4th line for the remainder of the
     sentence (Eric explicitly allowed this: "if that's not possible make
     it 4 lines, that's fine") but gives a clean anchor: "Infrastructure"
     is now always the LAST word before the wrap, sitting directly under
     the vacant space beside the tall clock digits — which is exactly
     where .n5i-readouts now sits. Self-limiting, matching .bl's own
     max-width in p3/home.html exactly minus 2*inset-x (this sits inside
     .bl's own padding) — see design-system.md "Fluid scaling" for the
     88.234375px measured nav-column constant + 24px gap this reserves
     against .br. */
  max-width: min(
    calc(590 * var(--n5i-u, 1px)),
    calc(100vw - 4 * var(--n5i-inset-x, 12px) - 112.234375px)
  );
  /* Deliberately its OWN literal size, not shared with .n5i-ap-stack span
     (Eric, 2026-09-09): first matched to the AM/PM font at 30px, then eased
     back down to 25px on Eric's call — do NOT re-couple this to the AM/PM
     size again, he explicitly wants them different now. */
  font: 400 calc(25 * var(--n5i-u, 1px))/1.25 var(--n5i-sans, 'Inter', system-ui, -apple-system, sans-serif);
  margin-top: calc(4 * var(--n5i-u, 1px));   /* + the cluster's scaled gap = 2x that from the label to the digit cap top */
}

/* the trailing timezone line reads as a separate footnote, not another
   line of the cluster — cluster-gap, not element-gap */
.n5i-tz-label {
  margin-top: calc(12 * var(--n5i-u, 1px));
}

.n5i-clock-inline {
  display: inline-block;
  vertical-align: baseline;
  /* overflow:hidden makes an inline-block's baseline its BOTTOM edge, and
     line-height:0 removes the paragraph's strut inside it — so the prose
     that follows sits exactly on the digits' baseline (the row bottom),
     as in the reference, instead of on the font's internal baseline
     ~13px above it. Nothing inside overflows: every digit is already
     clipped by its own column. */
  overflow: hidden;
  line-height: 0;
}
