/* Font paths are absolute (/assets/fonts/...) rather than relative to this
   stylesheet's own location. Functionally the two are equivalent here
   (external-stylesheet url()s resolve against the stylesheet's URL, not the
   page's), but wave 4 unifies all three @font-face sources — this file plus
   index.html's and portal.html's inline duplicates — on one convention so
   the pages that route deeper than "/" (e.g. /portal/marketmx) can't regress
   if a font-face block is ever copied between them. */
@font-face {
  font-family: "FusorType Signal";
  src: url("/assets/fonts/fusortype-signal/3.000/FusorTypeSignal-Variable.woff2") format("woff2");
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
}

@font-face {
  font-family: "FusorType Cipher";
  src: url("/assets/fonts/fusortype-cipher/0.300/FusorTypeCipher-Regular.woff2") format("woff2");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "FusorType Cipher";
  src: url("/assets/fonts/fusortype-cipher/0.300/FusorTypeCipher-Bold.woff2") format("woff2");
  font-style: normal;
  font-weight: 700;
  font-display: swap;
}

@font-face {
  font-family: "FusorType Cipher";
  src: url("/assets/fonts/fusortype-cipher/0.300/FusorTypeCipher-Italic.woff2") format("woff2");
  font-style: italic;
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "FusorType Cipher";
  src: url("/assets/fonts/fusortype-cipher/0.300/FusorTypeCipher-BoldItalic.woff2") format("woff2");
  font-style: italic;
  font-weight: 700;
  font-display: swap;
}

:root {
  color-scheme: light;

  --paper: #ededed;
  --paper-bright: #f7f7f7;
  --ink: #11120f;
  --graphite: #5d5e58;
  --muted: #66675f;
  --line: rgba(17, 18, 15, .34);
  --line-soft: rgba(17, 18, 15, .13);
  --signal: #dfff00;
  /* Text on signal. Signal is a fixed light colour, so its foreground is
     always the true dark — even on an inverted surface where --ink itself
     has flipped light. Never redefine this in .fia-inverse. */
  --on-signal: #11120f;
  --band: rgba(17, 18, 15, .08);
  /* Sample text in a field is an illustration, not an instruction — the
     label and the hint carry the requirement — so it sits well back. */
  --placeholder: rgba(17, 18, 15, .38);

  --inverse-line: rgba(237, 237, 237, .48);
  --inverse-line-soft: rgba(237, 237, 237, .25);
  --inverse-muted: rgba(237, 237, 237, .58);
  --inverse-text-strong: rgba(237, 237, 237, .92);
  --band-inverse: rgba(237, 237, 237, .12);

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;
  --space-24: 96px;

  --pad: clamp(18px, 2.45vw, 36px);
  --grid-gap: clamp(10px, .98vw, 14px);
  --page-grid: repeat(12, minmax(0, 1fr));
  --header-h: 52px;

  /* Both brand marks — the utility bar's masked glyph and the footer
     lockup — are ink, toned down to sit at the same weight as the band
     chips beside them rather than tinted to a different colour. They were
     inconsistent with each other (the bar's at .55, the footer's at full
     ink), so the footer lockup read as the heaviest thing on the page.
     The mark is TWO layers and the pair is the whole point. The frame —
     the solid beveled rectangle — is pure black at full strength. The
     counters cut out of it, which form the abstract "IA", are frosted
     paper. So one mark behaves two ways depending on what is behind it:
     on paper the black frame reads as a solid mark with the IA cut out of
     it; on ink the frame is black on black and DISAPPEARS, leaving just
     the lit IA floating.

     The black is --ink, not #000, and that is load-bearing twice over.
     Black-on-black only disappears when the two blacks are the same black:
     this palette's ink is #11120f, so a #000 frame sat 6.7% darker than
     every ink surface it crosses and rendered as a visibly darker
     rectangle instead of vanishing — measured, not assumed.

     And it is what lets the frame carry opacity at all. Ink composited
     over ink is ink at ANY alpha, so a translucent ink frame still
     vanishes perfectly on an ink surface, while on paper the same alpha
     tints it toward graphite — the tone the band chips' own labels use.
     One value, both behaviours, no per-surface override. .65 is where ink
     over paper lands on graphite (#5d5e58): 237 + .65 x (17 - 237) = 94.
     This is exactly why the earlier #000-at-40% failed — #000 over ink is
     not ink, so it showed as a grey shape rather than going. */
  --brand-mark-ink: var(--ink);
  --brand-mark-opacity: .65;

  /* Band scale (wave 6): every band — strip rows and in-table chips —
     shares one compact 24px visual height. Interactive bands keep a
     44px minimum touch target through an invisible hit-area extension
     (see .fia-band--interactive::after); the visible chip never grows
     for it. --band-frost is the translucent tint used by floating
     frosted chips (top navigation) that sit over scrolling content
     with their own backdrop blur instead of a solid header surface. */
  --band-row-h: 24px;
  --band-label-h: 24px;
  --band-frost: rgba(237, 237, 237, .66);

  --sans: "FusorType Signal", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: "FusorType Cipher", "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  --weight-display: 400;
  --weight-hero: 450;
  --weight-label: 650;
  --weight-utility: 400;
  --tracking-display: -.045em;
  --tracking-display-tight: -.055em;
  --tracking-copy: -.012em;

  --type-display: clamp(52px, 5vw, 148px);
  --type-project: clamp(64px, 8vw, 136px);
  --type-deck-title: clamp(32px, 4vw, 64px);
  /* The minimum is the 16px body floor, not a value under it — at any width
     where 1.05vw resolves lower, the clamp is what ships. */
  --type-body: clamp(16px, 1.05vw, 17px);
  --type-action: 16px;
  --type-label: clamp(10px, .72vw, 12px);

  --primary-action-height: 48px;
  --data-action-height: 56px;
  --ease: cubic-bezier(.16, 1, .3, 1);
  --wipe-ease: cubic-bezier(.76, 0, .24, 1);
  /* The page-switch motion's timing — see the fia-wipe/reveal keyframes
     below. Taken from the site index, which every other view swap now
     matches. */
  --wipe-dur: .4s;
  --wipe-content-delay: .18s;
  --reveal-step: 80ms;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html {
  min-width: 320px;
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  min-width: 320px;
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.25;
}

a { color: inherit; text-decoration: none; }
button,
input,
select,
textarea { color: inherit; font: inherit; }
button { cursor: pointer; }
p { text-wrap: pretty; }

::selection {
  color: var(--on-signal);
  background: var(--signal);
}

a:focus-visible,
button:focus-visible,
select:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 0;
  box-shadow: inset 0 0 0 2px var(--ink);
}

/* Text fields (wave 14, simplified per owner direction — supersedes the
   wave-11 two-tone ink+signal underline): the focused field takes the
   same band gray every .fia-band chip uses as its fill, and drops the
   outline + inset box entirely. Band gray alone is only ~1.2:1 against
   paper — nowhere near the 3:1 a focus indicator needs — so the field's
   own ink baseline rule carries the requirement instead: it thickens
   from 1px to 2px and is set explicitly to ink (in case a future field
   doesn't already inherit that at rest). Ink's contrast doesn't depend
   on the line's width, so this 2px ink rule measures the same as ink
   always has on this palette: 16.05:1 against paper, 17.54:1 against
   paper-bright, 13.58:1 against the new band-gray fill itself — all far
   past 3:1 regardless of which adjacent surface is used for the check.
   No outline, so nothing frames the text on any side. */
input:focus-visible,
textarea:focus-visible {
  outline: none;
  background: var(--band);
  border-bottom-style: solid;
  border-bottom-width: 2px;
  border-bottom-color: var(--ink);
}

.fia-grid {
  display: grid;
  grid-template-columns: var(--page-grid);
  gap: var(--grid-gap);
}

/* An inverted surface: the same page printed the other way round. Every rule
   on this site already speaks in --ink / --paper / --band / --line, so
   flipping those tokens on a container inverts the whole surface without
   touching a single component rule — the sprint intake and the client portal
   both use it, because the transition that hands over to them is ink and the
   destination should not snap back to paper.

   --on-signal deliberately does NOT flip: signal is a fixed light colour, so
   text on it is always the true dark. Anything that paints signal as a
   background must pair with --on-signal rather than --ink, or it inverts into
   light-on-signal at about 1.05:1.

   The greys are re-derived from paper rather than reused, because --graphite
   and --muted are mixes toward ink and would go the wrong way. */
.fia-inverse {
  --ink: #ededed;
  --paper: #11120f;
  --paper-bright: #1a1b17;
  --graphite: rgba(237, 237, 237, .72);
  --muted: rgba(237, 237, 237, .58);
  --line: rgba(237, 237, 237, .48);
  --line-soft: rgba(237, 237, 237, .25);
  --band: rgba(237, 237, 237, .12);
  --band-frost: rgba(17, 18, 15, .66);
  --placeholder: rgba(237, 237, 237, .38);
  background: var(--paper);
  color: var(--ink);
}

/* The brand mark is drawn as two paths in ONE svg, not as two
   independently-rasterized layers stacked on each other. It used to be a
   mask-composite'd span over a clip-path'd, backdrop-filtered ::before,
   and that pairing could never line up at the bevel: the clip-path was a
   percentage approximation of the artwork's own diagonal, so the two
   rasterizations left a visible sliver down the corner wherever the mark
   sat on ink — the "outer around the black part".

   One svg means one rasterization and exactly one antialiased edge: the
   counters over the frame. The frame is the artwork's outer subpath and
   the counters are its two inner subpaths, both verbatim. The frame is
   deliberately NOT drawn with its holes — the counters paint over it
   opaquely instead, which removes the doubled antialiasing a shared hole
   edge would otherwise produce.

   Cost, stated: the counters lose the backdrop blur the old ::before
   carried. They are opaque paper now rather than translucent frost, so
   nothing shows through to need blurring. */
.fia-brand-mark {
  display: block;
  height: var(--band-row-h);
  width: calc(var(--band-row-h) * 2.25);
}
.fia-brand-mark .brand-mark-frame {
  fill: var(--brand-mark-ink);
  fill-opacity: var(--brand-mark-opacity);
}
.fia-brand-mark .brand-mark-counters { fill: var(--paper); }

/* ONE page-switch motion, taken from the site index because that is the one
   that already read well: a panel travels down from the top over
   --wipe-dur on --wipe-ease, and the content behind it fades in
   --wipe-content-delay later. Everything that swaps a whole view uses it —
   the site index, the client-portal route cover, the sprint intake, and the
   portal's own views — so opening the menu, starting a project and entering
   the portal are all the same gesture.

   Two mechanisms for one motion, chosen by what the element holds. An empty
   panel scales (fia-wipe-in). A panel that already holds its content clips
   (fia-reveal-in/-out), because scaling would squash the very content it is
   revealing. Both travel top-down at the same rate, so they read identically.

   The index keeps CSS transitions rather than these animations for one
   reason: it toggles, and a transition reverses itself on the way out for
   free. Anything that animates in has to run fia-reveal-out explicitly to
   dismiss the same way. */
@keyframes fia-wipe-in {
  from { transform: scaleY(0); }
  to { transform: scaleY(1); }
}
@keyframes fia-reveal-in {
  from { clip-path: inset(0 0 100% 0); }
  to { clip-path: inset(0); }
}
@keyframes fia-reveal-out {
  from { clip-path: inset(0); }
  to { clip-path: inset(0 0 100% 0); }
}
@keyframes fia-wipe-out {
  from { transform: scaleY(1); }
  to { transform: scaleY(0); }
}
@keyframes fia-content-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes fia-content-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

.fia-action {
  min-height: var(--primary-action-height);
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  border: 1px solid var(--ink);
  border-radius: 0;
  padding: 0 var(--space-4);
  font-family: var(--sans);
  font-size: var(--type-action);
  font-weight: var(--weight-label);
  line-height: 1;
  letter-spacing: var(--tracking-copy);
  text-align: left;
  text-transform: none;
  cursor: pointer;
  transition:
    background-color .25s ease,
    color .25s ease,
    transform .2s var(--ease);
}

.fia-action--primary {
  background: var(--ink);
  color: var(--paper);
}

.fia-action--primary:hover,
.fia-action--primary:focus-visible {
  background: var(--signal);
  color: var(--on-signal);
}

.fia-action--secondary {
  background: transparent;
  color: var(--ink);
}

.fia-action--secondary:hover,
.fia-action--secondary:focus-visible {
  background: var(--ink);
  color: var(--paper);
}

.fia-action:active { transform: scale(.98); }
.fia-action:disabled,
.fia-action[aria-disabled="true"] {
  cursor: not-allowed;
  opacity: .5;
}

.fia-action-mark {
  display: inline-block;
  flex: 0 0 auto;
  font-size: 1.05em;
  font-weight: 400;
  transition: transform .3s var(--ease);
}

.fia-action:hover .fia-action-mark,
.fia-action:focus-visible .fia-action-mark {
  transform: translateX(4px);
}

/*
 * Label bands replace hairline division lines for metadata and utility
 * labels. A band is a filled rectangle that fills its grid cell/column
 * (never a text-hugging pill), so a band row functions as a division
 * line in its own right. See DESIGN.md > Foundations > Label bands.
 */
/*
 * Band-row text (wave 5): every band uses one Cipher label recipe — this
 * was already the footer's recipe (.site-footer-mid/.site-footer-right)
 * and is now the single spec for all band-row text site-wide. Contexts
 * that intentionally keep a different face/size (hero spec rail, sprint
 * terms strip, the site-index name column) carry their own more-specific
 * rule that wins over this default — see DESIGN.md > Label bands.
 */
.fia-band {
  display: block;
  padding: 5px 8px 4px;
  background: var(--band);
  border: 0;
  border-radius: 0;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.fia-band--inverse { background: var(--band-inverse); }
.fia-band--graphite { color: var(--graphite); }

/* The directional mark inside a band cell — the band-scale counterpart of
   .fia-action-mark. Every interactive band that navigates or opens
   something carries it, so a band cell's affordance reads the same
   wherever it appears. */
.fia-band-mark {
  display: inline-block;
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 400;
  background: transparent;
  transition: transform .3s var(--ease);
}

.fia-band--interactive:hover .fia-band-mark,
.fia-band--interactive:focus-visible .fia-band-mark {
  transform: translateX(3px);
}

.fia-band--interactive {
  position: relative;
  transition: color .2s ease-out .1s;
}

/* The visible chip is 24px; the touch target must not be. This invisible
   extension restores a 44px minimum hit area above and below the chip.
   Adjacent interactive bands need >=20px of clear space between them so
   extensions never overlap. ::after owns that hit area — the sweep below
   uses ::before instead so the two never collide. */
.fia-band--interactive::after {
  content: "";
  position: absolute;
  inset: -10px 0;
}

/* Directional wipe (wave 13), matching #runtime's .runtime-row: the fill
   arrives left-to-right and — because scaleX(0) with a fixed left origin
   is symmetric — retracts right-to-left on its own when it reverses,
   with no origin-switching needed. scaleX instead of .runtime-row's
   translateX(-102%): translateX needs overflow:hidden on the containing
   box to hide the off-screen rest position, and that would also clip
   ::after's intentionally-overflowing (inset: -10px 0) hit area outside
   the chip's own bounds, shrinking the touch target back under 44px.
   scaleX never extends past the element's own box, so no clipping is
   needed and ::after is untouched. z-index: -1 (rather than lifting the
   text to z-index: 1, as .runtime-row does) so bare-text chips (Menu,
   Close ×) don't need a wrapping <span> — non-positioned inline text
   already paints above a negative-z-index layer in the same stacking
   context, no markup change required. */
.fia-band--interactive::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--signal);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .55s var(--ease);
}
.fia-band--interactive:hover::before,
.fia-band--interactive:focus-visible::before {
  transform: scaleX(1);
}

.fia-band--interactive:hover,
.fia-band--interactive:focus-visible {
  color: var(--on-signal);
}

@media (max-width: 900px) {
  :root {
    --header-h: 50px;
  }
}

@media (max-width: 600px) {
  :root {
    --pad: 16px;
    --type-display: clamp(30px, 9.9vw, 42px);
    --type-project: clamp(48px, 15vw, 72px);
    --type-action: 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fia-action,
  .fia-action-mark,
  .fia-band-mark,
  .fia-band--interactive,
  /* The sweep lives on ::before, a separate generated box with its own
     transition — the element rule above does not reach it, so it needs
     its own entry to compress to the same near-instant swap. */
  .fia-band--interactive::before {
    transition-duration: .01ms !important;
  }
}
