/**
 * Noir Portfolio — base layer.
 *
 * Loaded after tokens.css, theme-noir.css and reset.css. Everything here reads
 * `--vc-*`; no literal colour or duration appears in this template, so a buyer
 * rebrands by editing tokens and nothing on the page misses the memo.
 *
 * Template-local values that are layout, not design — a measure, a gutter, the
 * display scale — are declared once here as `--noir-*` so the sections share
 * them.
 *
 * The art direction is brutalist editorial: display type sized off the
 * viewport rather than off a ramp, hard rules instead of shadows, square
 * corners, full-bleed bands that alternate between the two noir surfaces.
 * Three rules carry most of it — the display scale below, `.shell`, and
 * `[data-vc-surface]`.
 */

/**
 * Geist, self-hosted, with the system stack kept behind it in the same
 * declaration — not as a separate rule. If the woff2 fails to load, is still
 * loading, or the codepoint falls outside the subset, the browser walks this
 * list; a fallback in its own rule would never be reached.
 *
 * Overriding the token rather than the rules that use it is the whole point:
 * every component reads `--vc-font-sans`, so these lines re-typeset the page.
 * The @font-face blocks are in src/styles/fonts.css, which is synced — change
 * the face here, not there.
 *
 * `--vc-font-display` is headline-only and defined in tokens.css as an alias
 * of the body face. Pointing it at Anton is what separates the two roles: a
 * template that drops the Anton woff2 loses its headlines to Geist and stays
 * legible, because the fallback stack behind it is condensed-first — the
 * headline gets narrower, not four lines longer.
 */
:root {
  --vc-font-sans:
    "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --vc-font-mono:
    "Geist Mono", ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;
  --vc-font-display:
    "Anton", "Haettenschweiler", "Arial Narrow", Impact, var(--vc-font-sans);
}

:root {
  /**
   * The gutter is the only horizontal constraint left. There is no shell
   * width: the page runs edge to edge and the gutter is deliberately small,
   * because display type that stops short of the window is display type doing
   * half its job.
   *
   * Body copy is still capped — by `--noir-measure`, on the paragraphs
   * themselves. Full-bleed is an instruction about the band, not about the
   * line length inside it.
   */
  --noir-gutter: var(--vc-space-4);
  --noir-measure: 34rem;

  /* 2px, not 1px. A hairline is a whisper between two bands this loud, and it
     disappears entirely where a cream section meets a near-black one. */
  --noir-rule: 2px;
  --noir-header-h: 3.75rem;

  /**
   * The display scale.
   *
   * Sized off the viewport, not off `--vc-text-*`. The token ramp tops out at
   * 3rem because it exists to size interfaces; these are posters, and a poster
   * that steps between breakpoints re-measures every glyph mid-animation —
   * liquid-headline and drip-type both split their headline into per-character
   * boxes and read those boxes back. `clamp()` is continuous, so they measure
   * once.
   *
   * The preferred terms are what fill the window: 18vw puts the hero at about
   * thirteen characters to a line at desktop width, 10vw puts a section title
   * on one or two. The floors keep a 320px phone readable; the ceilings stop a
   * 2560px monitor from rendering a single word per screen.
   */
  --noir-display-hero: clamp(4rem, 18vw, 16rem);
  --noir-display-section: clamp(3rem, 10vw, 8rem);
  /* This rung is the only one set inside a box — card titles live in a card,
     and a 320px card has about 244px of inner width. Anton is condensed
     enough that `COUNTERWEIGHT` fits there at the 2rem floor; a wider display
     face would not, which is what `overflow-wrap` in work.css is there for. */
  --noir-display-sub: clamp(2rem, 5vw, 4rem);
  --noir-display-stat: clamp(3rem, 8vw, 6.5rem);

  /* Display type is set tighter than solid — at 256px the default 1.15 opens a
     gap you could park the next section in. 0.88 is the floor now that every
     display string is uppercase: caps have no descenders, so the clearance
     0.86 was buying for a `g` is clearance nothing needs. */
  --noir-display-leading: 0.88;
  /* Anton is already condensed, so the fit is the face's own. -0.01em closes
     the caps just enough to read as one mass without pulling the counters in
     `O` and `G` shut, which is what happens past about -0.03em. */
  --noir-display-tracking: -0.01em;

  /**
   * The rhythm around a display heading, shared by every band so the hero,
   * the section heads and the contact block all measure the same.
   *
   * The eyebrow is a ruled masthead line, so the gap under it is the gap under
   * the rule — tighter than the one below the heading, which has to separate
   * two different voices rather than a label from its subject.
   */
  --noir-head-gap: var(--vc-space-6);
  --noir-sub-gap: var(--vc-space-7);

  /**
   * One padding for every band, rather than a base value and a wider one past
   * 64rem. The old pair stepped 96px → 128px at exactly one width; this is
   * continuous, ends higher, and means a section's air is the same instruction
   * everywhere it is asked for.
   *
   * In rem rather than the px the brief names — identical at the default root
   * size, but it keeps scaling if a visitor has set a larger one, which px
   * would not.
   */
  --noir-band: clamp(6rem, 12vw, 10rem);
}

@media (min-width: 48rem) {
  :root {
    --noir-gutter: var(--vc-space-6);
    --noir-header-h: 4.25rem;
  }
}

html {
  scroll-behavior: smooth;
  /* Anchors land below the fixed header rather than under it. */
  scroll-padding-top: calc(var(--noir-header-h) + var(--vc-space-4));
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  /* Horizontal-handoff pins and translates; without this a sub-pixel overhang
     gives the whole page a sideways scrollbar on narrow screens. */
  overflow-x: hidden;
  background: var(--vc-bg);
  color: var(--vc-ink);
}

/**
 * The paint gate.
 *
 * Every string on this page arrives from content/site.config.js, so the markup
 * is empty until main.js has run. Painting that empty skeleton first and the
 * copy a moment later is a page-wide layout shift — measurably so: it is worth
 * about 0.18 of cumulative layout shift on a throttled phone, which is most of
 * a Lighthouse performance score.
 *
 * So the inline script in <head> sets `data-hydrating` and main.js clears it,
 * with a `load` listener as the backstop if main.js never runs. The window
 * between the two is a few milliseconds of blank, against a skeleton that had
 * no text in it anyway.
 *
 * With JavaScript disabled the inline script never runs, the attribute is
 * never set, and this rule never applies. The <noscript> notice explains the
 * empty page; it is not hidden behind the gate.
 */
[data-hydrating] body {
  visibility: hidden;
}

/* ── Surfaces ───────────────────────────────────────────────────────────── */

/**
 * `data-vc-surface` in the markup re-points the palette tokens for a section;
 * this is the rule that spends them. Two declarations, once, rather than a
 * background on every section stylesheet — and everything inside inherits,
 * including the components, which already read --vc-ink without knowing which
 * band they landed in.
 *
 * The bands butt straight up against each other with no rule between them.
 * Cream against near-black is a 17:1 edge; a border there would be a line
 * drawn on top of a line.
 */
[data-vc-surface] {
  background: var(--vc-bg);
  color: var(--vc-ink);
}

/* ── Layout ─────────────────────────────────────────────────────────────── */

/**
 * Full-bleed. The class is kept — every section still asks for `.shell`, and
 * the sections' own stylesheets do not need to know the layout changed — but
 * it no longer centres anything inside a column. It is a gutter, and that is
 * all it is.
 */
.shell {
  width: 100%;
  padding-inline: var(--noir-gutter);
}

.section {
  padding-block: var(--noir-band);
}

.section__head {
  margin-bottom: var(--vc-space-9);
}

/**
 * Section titles are the second voice of the page after the hero. Uppercase,
 * because Anton's caps are what make it read as a poster rather than a large
 * paragraph — and these are plain <h2>s with no component attached, so a CSS
 * transform is safe here in a way it is not on the two animated headlines.
 *
 * `font-weight: 400` is load-bearing. Anton ships one weight; any heavier
 * number gets it synthesised-bold, which at this size is a visible smear.
 */
.section__title {
  margin-top: var(--noir-head-gap);
  font-family: var(--vc-font-display);
  font-size: var(--noir-display-section);
  font-weight: 400;
  line-height: var(--noir-display-leading);
  letter-spacing: var(--noir-display-tracking);
  text-transform: uppercase;
  text-wrap: balance;
}

.section__intro {
  max-width: var(--noir-measure);
  margin-top: var(--noir-sub-gap);
  color: var(--vc-ink-muted);
  font-size: var(--vc-text-lg);
}

@media (min-width: 48rem) {
  .section__intro {
    font-size: var(--vc-text-xl);
  }
}

/* ── Type ───────────────────────────────────────────────────────────────── */

/**
 * The eyebrow carries the section number, and against type this large a 12px
 * label is not a counterweight — it is a crumb. Sized up, ruled underneath,
 * and run the full width of the band so it reads as a masthead line.
 */
.eyebrow {
  display: block;
  padding-bottom: var(--vc-space-3);
  border-bottom: var(--noir-rule) solid var(--vc-ink);
  color: var(--vc-ink);
  font-family: var(--vc-font-mono);
  font-size: var(--vc-text-sm);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* ── Controls ───────────────────────────────────────────────────────────── */

/**
 * Square, hard-edged, and set in the mono face — a pill button would be the
 * one soft object on a page built out of rectangles.
 */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3rem;
  padding: var(--vc-space-3) var(--vc-space-6);
  border: var(--noir-rule) solid var(--vc-ink);
  border-radius: 0;
  color: var(--vc-ink);
  font-family: var(--vc-font-mono);
  font-size: var(--vc-text-sm);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  transition:
    background-color var(--vc-dur-base) var(--vc-ease-out),
    color var(--vc-dur-base) var(--vc-ease-out),
    border-color var(--vc-dur-base) var(--vc-ease-out);
}

.button--solid,
.button[data-variant="solid"] {
  background: var(--vc-ink);
  color: var(--vc-bg);
}

.button[data-variant="ghost"] {
  background: transparent;
  color: var(--vc-ink);
}

.button--solid:hover,
.button[data-variant="solid"]:hover {
  background: var(--vc-accent);
  border-color: var(--vc-accent);
  /* The accent is a mid-tone: it takes the near-black ink at 4.2:1 on the
     light band and 8.7:1 on the dark one, where cream would be 2.4:1. */
  color: var(--vc-bg);
}

.button[data-variant="ghost"]:hover {
  background: var(--vc-ink);
  color: var(--vc-bg);
}

/* ── Tags ───────────────────────────────────────────────────────────────── */

.tag {
  display: inline-flex;
  align-items: center;
  padding: var(--vc-space-1) var(--vc-space-3);
  border: 1px solid var(--vc-border);
  border-radius: 0;
  color: var(--vc-ink-muted);
  font-family: var(--vc-font-mono);
  font-size: var(--vc-text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* ── Skip link ──────────────────────────────────────────────────────────── */

.skip-link {
  position: absolute;
  top: var(--vc-space-3);
  left: var(--vc-space-3);
  z-index: calc(var(--vc-z-overlay) + 1);
  padding: var(--vc-space-3) var(--vc-space-5);
  background: var(--vc-ink);
  color: var(--vc-bg);
  font-family: var(--vc-font-mono);
  font-size: var(--vc-text-sm);
  text-decoration: none;
  /* Off-screen until focused, rather than display:none — a hidden element is
     not focusable, so a keyboard user would never reach it. */
  transform: translateY(-200%);
  transition: transform var(--vc-dur-fast) var(--vc-ease-out);
}

.skip-link:focus-visible {
  transform: translateY(0);
}

/* ── No-script notice ───────────────────────────────────────────────────── */

.noscript {
  padding: var(--vc-space-6) var(--noir-gutter);
  border-top: var(--noir-rule) solid var(--vc-border);
  background: var(--vc-surface);
  color: var(--vc-ink-muted);
  font-size: var(--vc-text-sm);
  text-align: center;
}

/* ── Links ──────────────────────────────────────────────────────────────── */

a:hover {
  color: var(--vc-accent);
}
