/* NIGHTFALL shared site chrome — injected into every page by render.py's
   document shell, after each page's own styles. Only df- prefixed
   components live here so nothing can collide with per-page CSS; token
   fallbacks match the identity spec for any page whose :root differs. */

/* ============================================================
   SITE CHROME — the command rail (console pages) + top nav (public).
   Server-rendered by server/nav.py; site.js adds drawer behavior only.
   Z-SCALE (the one true ladder): page shell <=3 · classif 12 · scrim 14 ·
   rail/topnav 15 · page gates 20 · responses btn 8900 · drawer state uses
   the same rail element · toasts 9000. Chrome never uses backdrop-filter
   (it created the old stacking-context bugs).
   ============================================================ */

.df-skip {
  position: fixed; left: -999px; top: 24px; z-index: 100;
  background: #121110; color: var(--fg, #E8E4DA);
  border: 1px solid var(--red, #E32636);
  font-family: 'Cascadia Mono', ui-monospace, Consolas, monospace;
  font-size: 10px; letter-spacing: .22em; text-transform: uppercase;
  padding: 10px 18px; text-decoration: none;
}
.df-skip:focus { left: 250px; }

.df-brand {
  display: flex; align-items: center; gap: 10px;
  font-family: Bahnschrift, 'Arial Narrow', sans-serif; font-weight: 700;
  letter-spacing: .16em; font-size: 17px;
  color: var(--fg, #E8E4DA); text-decoration: none;
}
.df-brand b { color: var(--red, #E32636); }
.df-brand img { width: 25px; height: 25px; display: block; border: 1px solid #2b2823; }

/* ---- the command rail ---- */
.df-rail {
  position: fixed; left: 0; top: 18px; bottom: 0; width: 232px; z-index: 15;
  box-sizing: border-box; overflow-y: auto; scrollbar-width: thin;
  background: #0f0e0d; border-right: 1px solid #2b2823;
  padding: 18px 18px 14px; display: flex; flex-direction: column; gap: 4px;
  font-family: 'Cascadia Mono', ui-monospace, Consolas, monospace;
}
body.df-console { padding-left: 232px; }
.df-rail .df-brand { margin-bottom: 14px; }
.df-rg { border-top: 1px solid #1c1a17; padding: 10px 0 8px; }
.df-rg-h {
  font-size: 8.5px; letter-spacing: .3em; text-transform: uppercase;
  color: var(--red, #E32636); margin-bottom: 6px;
}
.df-rg-pub .df-rg-h { color: var(--faint, #837d71); }
.df-rl {
  display: flex; align-items: center; gap: 8px;
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--muted, #b5afa3); text-decoration: none; padding: 6px 8px;
  border-left: 2px solid transparent; transition: color .15s, background .15s;
}
.df-rl:hover { color: var(--fg, #E8E4DA); background: #14120f; }
.df-rl.cur {
  color: var(--fg, #E8E4DA); border-left-color: var(--red, #E32636);
  background: #14120f;
}
/* Route marks in the rail and top nav. Painted as a mask so the shape
   takes currentColor — the icon files theme themselves against the OS,
   which would strand them near-black on the black rail for anyone whose
   machine is in light mode. As a mask they also follow hover and .cur. */
.df-ri {
  flex: none; width: 16px; height: 16px; display: inline-block;
  background-color: currentColor; opacity: .72;
  -webkit-mask: var(--m) center / contain no-repeat;
  mask: var(--m) center / contain no-repeat;
  transition: opacity .15s;
}
.df-rl:hover .df-ri, .df-rl.cur .df-ri { opacity: 1; }

/* Status marks. Same mask technique as the route marks above, for the same
   reason: an SVG behind <img> answers prefers-color-scheme against the OS,
   not this page. Taking currentColor also means a caller keeps whatever
   semantic colour class it already had and gains a distinct silhouette on
   top of it — so a status stops being legible by hue alone. Six of these
   replaced unicode diamonds that differed by a single shaded quadrant. */
.df-sm {
  display: inline-block; width: 14px; height: 14px; vertical-align: -2px;
  flex: none; background-color: currentColor;
  -webkit-mask: var(--m) center / contain no-repeat;
  mask: var(--m) center / contain no-repeat;
}
.df-sm.lg { width: 16px; height: 16px; }
/* a mark beside a word: give it breathing room without a wrapper element */
.df-sm + span, .df-sm + b, .df-sm + i { margin-left: 6px; }
.df-rl.df-anchor { font-size: 10px; color: var(--faint, #837d71); }
.df-rl.df-anchor:hover { color: var(--muted, #b5afa3); }
.df-rl.df-pub { font-size: 10px; padding: 4px 8px; }
.df-rl.locked { opacity: .45; }
.df-lock {
  margin-left: auto; font-size: 8px; letter-spacing: .24em;
  border: 1px solid #2b2823; padding: 1px 6px; color: var(--faint, #837d71);
}
/* whichever of these two ends up first takes the slack above it */
.df-whatsnew { margin-top: auto; }
.df-whatsnew + .df-op { margin-top: 12px; }
.df-new {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: none; border: 1px solid #2b2823; border-left: 2px solid var(--red, #E32636);
  color: var(--fg, #E8E4DA); font-family: inherit;
  font-size: 10px; letter-spacing: .1em; line-height: 1.5; padding: 8px 10px;
}
.df-new:hover { border-color: var(--red, #E32636); }
.df-new .d {
  display: block; color: var(--faint, #837d71); font-size: 8.5px;
  letter-spacing: .22em; margin-top: 3px;
}
.df-op {
  margin-top: auto; border-top: 1px solid #2b2823; padding-top: 12px;
  display: flex; flex-direction: column; gap: 6px;
}
.df-op-name {
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--fg, #E8E4DA);
}
.df-op-name.dim { color: var(--faint, #837d71); }
.df-op-chip {
  align-self: flex-start; font-size: 8.5px; letter-spacing: .24em;
  text-transform: uppercase; color: var(--red, #E32636);
  border: 1px solid #4a1219; padding: 2px 8px;
}
.df-op-out, .df-op-in {
  font-size: 9.5px; letter-spacing: .2em; text-transform: uppercase;
  color: var(--muted, #b5afa3); text-decoration: none;
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: inherit;
}
.df-op-in { color: var(--red, #E32636); }
.df-op-out:hover, .df-op-in:hover { color: var(--fg, #E8E4DA); }

/* drawer toggle + scrim (hidden on desktop) */
.df-menu {
  display: none; position: fixed; left: 14px; top: 26px; z-index: 16;
  background: #121110; border: 1px solid #2b2823; cursor: pointer;
  color: var(--fg, #E8E4DA);
  font-family: 'Cascadia Mono', ui-monospace, Consolas, monospace;
  font-size: 9px; letter-spacing: .28em; text-transform: uppercase;
  padding: 8px 13px;
}
.df-menu:hover { border-color: var(--red, #E32636); }
.df-scrim {
  position: fixed; inset: 0; z-index: 14; background: rgba(5,5,5,.62);
}
@media (max-width: 1100px) {
  body.df-console { padding-left: 0; }
  .df-rail { transform: translateX(-100%); transition: transform .22s ease; }
  .df-rail.open { transform: none; }
  .df-menu { display: block; }
  @media (prefers-reduced-motion: reduce) {
    .df-rail { transition: none; }
  }
}

/* ---- public top nav ---- */
.df-topnav {
  position: fixed; top: 18px; left: 0; right: 0; z-index: 15;
  background: linear-gradient(rgba(10,10,10,.96), rgba(10,10,10,.88));
  border-bottom: 1px solid #2b2823;
  font-family: 'Cascadia Mono', ui-monospace, Consolas, monospace;
}
.df-top-in {
  max-width: 1280px; margin: 0 auto; padding: 0 24px; height: 54px;
  display: flex; align-items: center; gap: 22px;
}
.df-top-in .df-brand { margin-right: auto; }
.df-top-links { display: flex; gap: 18px; }
.df-tl {
  font-size: 11px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--muted, #b5afa3); text-decoration: none; padding: 4px 0;
  border-bottom: 1px solid transparent;
  display: inline-flex; align-items: center; gap: 7px;
}
.df-tl .df-ri { width: 14px; height: 14px; }
.df-tl:hover .df-ri, .df-tl[aria-current="page"] .df-ri { opacity: 1; }
.df-tl:hover { color: var(--fg, #E8E4DA); border-color: var(--red, #E32636); }
.df-tl[aria-current="page"] { color: var(--fg, #E8E4DA); border-color: var(--red, #E32636); }
.df-cta {
  font-size: 10px; letter-spacing: .2em; text-transform: uppercase;
  color: var(--red, #E32636); text-decoration: none;
  border: 1px solid #8f1523; padding: 8px 14px;
}
.df-cta:hover { background: var(--red, #E32636); color: #fff; }
/* the store is a permanent fixture in both chromes — it is the org's only
   sales surface, and it used to be a link buried among the page list */
.df-cta-shop { color: var(--amber, #c9a45c); border-color: #57451f; }
.df-cta-shop:hover { background: var(--amber, #c9a45c); color: #0A0A0A; }
/* the TTK Terminal CTA: a fixture like the Store, in its own steel-blue
   dress - one colour per fixture, and reserved red stays for warnings */
.df-cta-ttk { color: #7fa8c9; border-color: #2e4a5c; }
.df-cta-ttk:hover { background: #7fa8c9; color: #0A0A0A; }
.df-shop {
  display: block; margin-top: 8px; text-align: center; text-decoration: none;
  border: 1px solid #57451f; color: var(--amber, #c9a45c);
  font-size: 9.5px; letter-spacing: .2em; text-transform: uppercase;
  padding: 7px 10px;
}
.df-shop:hover { background: var(--amber, #c9a45c); color: #0A0A0A; }
.df-cta-dis { color: var(--muted, #b5afa3); border-color: #2b2823; }
.df-cta-dis:hover { background: none; color: var(--fg, #E8E4DA); border-color: var(--red, #E32636); }
.df-anchors {
  max-width: 1280px; margin: 0 auto; padding: 7px 24px;
  display: flex; gap: 18px; overflow-x: auto; scrollbar-width: none;
  border-top: 1px solid #1c1a17;
}
.df-anchors a {
  font-size: 9.5px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--faint, #837d71); text-decoration: none; white-space: nowrap;
}
.df-anchors a:hover { color: var(--fg, #E8E4DA); }
@media (max-width: 900px) {
  .df-top-links { display: none; }
}

/* ---- public mobile drawer (topnav pages; deep scan 2026-09-06) ----
   The rail's drawer mechanics, pointed at a public panel. The top-variant
   menu button appears only once the link row is gone (<=900px), never in
   the 901-1100px band where the console rule would otherwise show it. */
.df-menu.df-menu-top { display: none; }
.df-topdrawer {
  position: fixed; top: 0; left: 0; bottom: 0; z-index: 15;
  width: min(78vw, 320px); overflow-y: auto;
  background: #0f0e0d; border-right: 1px solid #2b2823;
  padding: 64px 16px 24px; display: flex; flex-direction: column;
  transform: translateX(-102%); transition: transform .22s ease;
}
.df-topdrawer.open { transform: translateX(0); }
.df-topdrawer a.df-td {
  display: block; padding: 11px 10px; text-decoration: none;
  color: var(--muted, #b5afa3);
  font-family: 'Cascadia Mono', ui-monospace, Consolas, monospace;
  font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
  border-bottom: 1px solid rgba(43, 40, 35, .55);
}
.df-topdrawer a.df-td:hover,
.df-topdrawer a.df-td[aria-current="page"] { color: var(--fg, #E8E4DA); }
.df-topdrawer a.df-td[aria-current="page"] {
  border-left: 2px solid var(--red, #E32636); padding-left: 8px;
}
@media (max-width: 900px) {
  .df-menu.df-menu-top { display: block; }
  /* the menu button is fixed over the start of the row: move the wordmark
     clear of it (it sat under the button, clipped to "IGHT FALL") */
  .df-top-in { padding-left: 86px; padding-right: 16px; gap: 12px; }
  /* every CTA is repeated in the drawer, so they leave the row before they
     can push the page sideways: TTK and Discord first, then the rest */
  .df-top-in .df-cta-ttk, .df-top-in .df-cta-dis { display: none; }
}
@media (max-width: 600px) {
  .df-top-in .df-cta { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .df-topdrawer { transition: none; }
}

/* chrome focus ring — the old dropdown summaries had none */
.df-rail a:focus-visible, .df-topnav a:focus-visible,
.df-menu:focus-visible, .df-skip:focus-visible {
  outline: 1px solid var(--red, #E32636); outline-offset: 2px;
}

/* ---- windowed lists ---- */
/* The footer of a list that is showing you part of itself. Says the real
   total, because "showing 50 of 403" and "50 results" mean very different
   things to someone deciding whether to search instead of scroll. */
.df-more {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px;
  border-top: 1px solid #1c1a17; margin-top: 10px; padding-top: 10px;
}
.df-more span {
  font-size: 9.5px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--faint, #837d71);
}
.df-more button {
  font-family: inherit; font-size: 9.5px; letter-spacing: .18em;
  text-transform: uppercase; background: none; cursor: pointer;
  border: 1px solid #2b2823; color: var(--muted, #b5afa3); padding: 6px 14px;
}
.df-more button:hover { color: var(--fg, #E8E4DA); border-color: var(--red, #E32636); }
.df-more button:focus-visible { outline: 1px solid var(--red, #E32636); outline-offset: 2px; }

/* ---- the industry chain ---- */
/* One strip across the four industry consoles. Empty until site.js fills
   it, so a page that loads without the runtime shows nothing rather than
   a broken skeleton. */
.df-chain {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin-top: 16px; padding-top: 12px; border-top: 1px solid #1c1a17;
}
.df-chain-k {
  font-size: 9px; letter-spacing: .24em; text-transform: uppercase;
  color: var(--faint, #837d71); margin-right: 4px;
}
.df-chain-s {
  font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--muted, #b5afa3); text-decoration: none;
  border: 1px solid #2b2823; padding: 5px 9px; white-space: nowrap;
}
a.df-chain-s:hover { color: var(--fg, #E8E4DA); border-color: var(--red, #E32636); }
.df-chain-s.here {
  color: var(--fg, #E8E4DA); border-color: var(--red, #E32636);
  background: rgba(227, 38, 54, .07);
}
.df-chain-s.soon { color: var(--faint, #837d71); border-style: dashed; }
.df-chain-i {
  width: 10px; height: 1px; background: #2b2823; flex: none;
}
@media (max-width: 700px) {
  .df-chain-k { width: 100%; margin-bottom: 2px; }
  .df-chain-i { display: none; }
}

/* ---- ambient effects (all reduced-motion safe) ---- */
.df-motes {
  position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: .6;
}
.df-rvx { opacity: 0; transform: translateY(14px);
  transition: opacity .6s ease, transform .6s ease; }
.df-rvx.on { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .df-rvx { opacity: 1; transform: none; transition: none; }
}

/* targeting-reticle hover — corner brackets on the interactive rows every
   console uses (harmless where a class doesn't exist) */
.mrow, .orerow, .orrow, .brow, .rrow, .scard { position: relative; }
.mrow:hover::after, .orerow:hover::after, .orrow:hover::after,
.brow:hover::after, .rrow:hover::after, .scard:hover::after {
  content: ""; position: absolute; inset: 1px; pointer-events: none;
  background:
    linear-gradient(var(--red, #E32636), var(--red, #E32636)) top left / 8px 1.5px,
    linear-gradient(var(--red, #E32636), var(--red, #E32636)) top left / 1.5px 8px,
    linear-gradient(var(--red, #E32636), var(--red, #E32636)) bottom right / 8px 1.5px,
    linear-gradient(var(--red, #E32636), var(--red, #E32636)) bottom right / 1.5px 8px;
  background-repeat: no-repeat; opacity: .75;
}

/* classification strip glitch — one frame of static every ~9s */
@keyframes df-classif-glitch {
  0%, 96.6%, 100% { opacity: 1; transform: none; }
  96.9% { opacity: .78; transform: translateX(.6px); }
  97.2% { opacity: 1; transform: translateX(-.4px); }
  97.4% { opacity: .9; transform: none; }
}
.classif { animation: df-classif-glitch 9.3s steps(1) infinite; }

/* THE classification strip — one rule, one z (spec ladder: classif 12).
   Pages carried 24 drifted private copies (z 12 to 30, and below ~600px
   the strip could wrap into a second line that ate the nav). nowrap +
   ellipsis make a narrow viewport trim the text instead. */
.classif {
  position: fixed; top: 0; left: 0; right: 0; z-index: 12;
  background: #C8102E; color: #E8E4DA;
  font-size: 9px; letter-spacing: .3em; text-transform: uppercase;
  text-align: center; padding: 3px 8px; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.classif .cfile { position: absolute; right: 12px; top: 3px; opacity: .8; letter-spacing: .24em; }
/* the file tag is pinned right and the strip's text is centred: on a phone
   the two land on each other, so the tag goes */
@media (max-width: 700px) { .classif .cfile { display: none; } }


/* barcode serials — scanner pass on hover only */
.bcode:hover {
  background-image: linear-gradient(90deg,
    transparent 0 30%, rgba(232,228,218,.25) 34%, transparent 38% 100%);
  background-size: 220% 100%;
  animation: df-bcode-scan .9s linear;
}
@keyframes df-bcode-scan {
  from { background-position: -60% 0; } to { background-position: 160% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .classif { animation: none; }
  .bcode:hover { animation: none; background-image: none; }
}

/* ---- toasts ---- */
.df-toasts {
  position: fixed; right: 18px; bottom: 18px; z-index: 9000;
  display: flex; flex-direction: column; gap: 8px; max-width: 340px;
}
.df-toast {
  background: #121110; border: 1px solid #2b2823;
  border-left: 3px solid var(--red, #E32636);
  color: var(--fg, #E8E4DA);
  font-family: 'Cascadia Mono', ui-monospace, Consolas, monospace;
  font-size: 11px; line-height: 1.5; letter-spacing: .06em;
  padding: 10px 14px;
  animation: df-toast-in .25s ease-out;
}
.df-toast.ok { border-left-color: #4d7c53; }
.df-toast.warn { border-left-color: #c9a45c; }
@keyframes df-toast-in { from { opacity: 0; transform: translateY(6px); } }
@media (prefers-reduced-motion: reduce) { .df-toast { animation: none; } }

/* ---- command responses (reviewed-action outcomes) ---- */
.df-resp-btn {
  position: fixed; right: 18px; bottom: 18px; z-index: 8900;
  background: #121110; border: 1px solid #2b2823;
  color: var(--fg, #E8E4DA); cursor: pointer;
  font-family: 'Cascadia Mono', ui-monospace, Consolas, monospace;
  font-size: 9px; letter-spacing: .28em; text-transform: uppercase;
  padding: 9px 14px; display: flex; align-items: center; gap: 8px;
}
.df-resp-btn:hover { border-color: var(--red, #E32636); }
.df-resp-btn .n {
  background: var(--red, #E32636); color: #0A0A0A;
  font-weight: 700; letter-spacing: 0;
  min-width: 16px; height: 16px; line-height: 16px;
  text-align: center; font-size: 10px;
}
/* Both of these live in the bottom-right corner at bottom:18px, and the
   toast stack (z 9000) outranks the button (z 8900) — so a toast painted
   straight over "Responses" and its unread count. The old rule here tried
   to fix that with a sibling selector AND set bottom to the value it
   already had, so it was a no-op twice over; the stack is also created
   lazily on the first toast, so it can land either side of the button in
   the DOM and no sibling selector is reliable. site.js sets .df-has-resp
   on <body> when the button exists; the toasts stack above it. */
body.df-has-resp .df-toasts { bottom: 64px; }

.df-drawer {
  position: fixed; right: 18px; bottom: 56px; z-index: 8950;
  width: min(400px, calc(100vw - 36px)); max-height: 60vh; overflow-y: auto;
  background: #0f0e0d; border: 1px solid #2b2823;
  font-family: 'Cascadia Mono', ui-monospace, Consolas, monospace;
}
.df-drawer h6 {
  margin: 0; padding: 10px 14px;
  border-bottom: 1px solid #2b2823;
  font-size: 9px; letter-spacing: .3em; text-transform: uppercase;
  color: var(--muted, #b5afa3); font-weight: 400;
  position: sticky; top: 0; background: #0f0e0d;
}
.df-dec { padding: 10px 14px; border-bottom: 1px solid #1c1a17; }
.df-dec:last-child { border-bottom: 0; }
.df-dec .st {
  font-size: 9px; letter-spacing: .24em; text-transform: uppercase;
  font-weight: 700;
}
.df-dec.ok .st { color: #7da583; }
.df-dec.no .st { color: var(--red, #E32636); }
.df-dec .what {
  color: var(--fg, #E8E4DA); font-size: 11px; letter-spacing: .04em;
  margin: 3px 0 0; line-height: 1.5;
}
.df-dec .meta {
  color: var(--faint, #837d71); font-size: 9px; letter-spacing: .12em;
  margin-top: 3px;
}
.df-dec .note {
  color: var(--muted, #b5afa3); font-size: 10px; letter-spacing: .04em;
  margin-top: 5px; padding-left: 8px;
  border-left: 2px solid #2b2823; line-height: 1.5;
}

/* ---- numbered section band ----
   Was copy-pasted into eight templates, which had drifted into two
   near-identical variants (a 120px tab with a 24px numeral, and a 130px
   tab with a 26px one). Consolidated on the wider pair, which is what the
   brand reference documents. Consoles that scope their own colours still
   win through the custom properties. */
.secband {
  border-top: 1px solid var(--line, #2b2823); position: relative;
  margin: 6px 0 26px; padding-top: 16px;
  display: flex; align-items: baseline; gap: 16px;
}
.secband::before {
  content: ""; position: absolute; top: -2px; left: 0;
  width: 130px; height: 3px; background: var(--red, #E32636);
}
.secband .num {
  font-family: var(--disp, "Bahnschrift", sans-serif); font-weight: 700;
  font-size: 26px; color: var(--red, #E32636); letter-spacing: .04em;
}
.secband .lbl {
  font-size: 11px; letter-spacing: .38em; text-transform: uppercase;
  color: var(--bone, #E8E4DA);
  /* the label is a real h2 since the T8 pass - ~20 pages had no heading
     outline at all. These zero the UA heading styles so the promotion
     renders pixel-identical to the old span. */
  margin: 0; font-weight: 400; font-family: inherit;
}
.secband .sub {
  margin-left: auto; font-size: 9px; letter-spacing: .2em;
  text-transform: uppercase; color: var(--faint, #837d71);
}

/* division / specialisation insignia beside a heading. flex:none applies in
   a flex eyebrow, vertical-align in an inline heading; each is inert in the
   other context, so one rule covers both callers. */
.dmark { flex: none; opacity: .9; vertical-align: -3px; }

/* ---- rank pips ----
   Three ascending bars; earned ones lit, command scarlet, LC amber. S6
   deliberately draws none - command tier is stated, not counted. The rank
   TEXT always stays beside the pips, so this is never the only way to read
   a rank. Was copy-pasted into marines and fleetcom; one definition now. */
.pip { display: inline-flex; gap: 2px; align-items: flex-end; height: 11px;
  margin-right: 5px; vertical-align: -1px; }
.pip i { display: block; width: 3px; background: var(--muted, #b5afa3); }
.pip i.h1 { height: 4px; } .pip i.h2 { height: 7px; } .pip i.h3 { height: 10px; }
.pip i.on { background: var(--bone, #E8E4DA); }
.pip i.cmd { background: var(--red, #E32636); }
.pip i.lc { background: var(--amber, #c9a45c); }
.pip i.off { background: transparent; border: 1px solid var(--line, #2b2823); }
