/* ==========================================================================
   Base, layout and components. Plain CSS on the tokens in tokens.css.
   Logical properties throughout, so the RTL layer stays short.
   ========================================================================== */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  /* The page is dark, and without this the browser paints its own furniture
     light on top of it: scrollbars, the textarea resize grip, autofill
     backgrounds, date and select popups. One declaration, and it is the only
     way to reach chrome that CSS does not own. */
  color-scheme: dark;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;

  /* The header is sticky, so without this every anchor jump and every focused
     element scrolled to the top of the viewport lands underneath it — WCAG 2.2
     AA 2.4.11. Measured: the header is 82px while the nav wraps onto two rows
     and 57px above that, so the value is not one number. It was the skip link
     that made this worth fixing: the one control whose entire purpose is
     keyboard access was jumping to a #main that the header then covered. */
  scroll-padding-top: 6.4rem;
}

@media (min-width: 641px) {
  html { scroll-padding-top: 4.6rem; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

img, svg, video { max-width: 100%; height: auto; display: block; }

a { color: var(--brass); text-underline-offset: 0.18em; }
a:hover { color: var(--text); }

:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 3px;
}

/* The two-brass rule applies to the focus ring too, and this is the place it
   was missed. outline-offset draws the ring OUTSIDE the control, so on a light
   section it lands on --paper, where --brass measures 1.91:1 — the ring was
   there and effectively invisible. That defeats keyboard navigation on exactly
   the sections carrying the calls to action. --brass-deep measures 5.23:1. */
.section--paper :focus-visible,
.statement:not(.section) :focus-visible {
  outline-color: var(--brass-deep);
}

/* Offset vertically, not horizontally: inset-inline-start flips under
   dir="rtl" and an off-canvas element on the inline axis then pushes the
   page sideways on every Persian page. */
.skip-link {
  position: absolute;
  inset-inline-start: 0;
  top: -5rem;
  z-index: 100;
  background: var(--brass);
  color: var(--ink-on-brass);
  padding: 0.75rem 1.25rem;
  font-family: var(--font-mono);
  font-size: var(--fs-small);
}
.skip-link:focus { top: 0; }

/* -- Layout ---------------------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--max-width);
  margin-inline: auto;
  padding-inline: var(--space-gutter);
}

.section { padding-block: var(--space-section); }

/* Light sections. Every colour is re-declared from the paper token set —
   never inherited from the ink set, or text lands on the wrong ground. */
.section--paper {
  background: var(--paper);
  color: var(--text-ink);
}
.section--paper a { color: var(--brass-deep); }
.section--paper a:hover { color: var(--text-ink); }
.section--paper .eyebrow { color: var(--steel-deep); }
.section--paper .lede { color: var(--text-ink); }
.section--paper .dim { color: var(--text-ink-dim); }

/* -- Type ------------------------------------------------------------------ */

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 400;
  text-transform: uppercase;
  text-wrap: balance;
  margin: 0;
}

h1 {
  font-size: var(--fs-hero);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
}

h2 {
  font-size: var(--fs-h2);
  line-height: 0.94;
  letter-spacing: -0.025em;
}

h3 {
  font-size: var(--fs-h3);
  line-height: var(--lh-snug);
  letter-spacing: 0.01em;
}

p { margin: 0 0 1em; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--text);
  /* Lede paragraphs read at a shorter measure than body prose. Set here
     rather than as a max-width on each one, which is how four slightly
     different values (46ch, 50ch, 52ch, 56ch) ended up in templates. */
  max-width: 52ch;
}

.dim { color: var(--text-dim); }
.mono { font-family: var(--font-mono); }

/* Some eyebrows are <h2> — see the CV, where they are the only thing standing
   between an h1 and a page of h3s. The class has to beat every h2 rule,
   line-height included. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--steel);
  margin: 0 0 1.1rem;
}

/* The accent word in the hero. The display face in brass — not an italic
   serif, which would cost a 64KB font file for one word. */
.accent { color: var(--brass); }

/* -- Header ---------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--ink) 92%, transparent);
  backdrop-filter: blur(8px);
  border-block-end: 1px solid var(--line-soft);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: 0.9rem;
}

.brand {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  text-decoration: none;
  color: var(--text);
}
.brand svg { width: 30px; color: var(--brass); flex: none; }
.brand-name {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: 0.82rem;
  letter-spacing: 0.02em;
  line-height: 1;
}
.brand-tag {
  font-family: var(--font-mono);
  font-size: 0.6563rem;  /* was 9px: below what most people read comfortably */
  letter-spacing: 0.17em;
  text-transform: uppercase;
  color: var(--steel);
  margin-top: 0.28rem;
}

.site-nav { display: flex; align-items: center; gap: 1.4rem; }

/* The padding is the point: these links measured 17.8px tall, and WCAG 2.2 AA
   2.5.8 wants 24. Block padding grows the pointer target to 26px and changes
   nothing visually, which is the right trade — shrinking the type to make room
   would have been the wrong one. */
.site-nav a {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 26px;
  /* min-width for one link only: "CV" measured 16.1px across, the single
     target on the site narrow enough to fail 2.5.8 on its own axis. */
  min-width: 26px;
  padding-block: 0.25rem;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--text-dim);
  transition: color var(--dur-fast) var(--ease);
}
/* Extends the pointer target to 44px without making the header 18px taller.
   The box stays 26px, which is what the layout sees; the hit area is the
   pseudo-element, which nothing lays out. Symmetric insets, so it needs no
   RTL variant. */
.site-nav a::after {
  content: '';
  position: absolute;
  inset: -9px -4px;
}

.site-nav a:hover,
.site-nav a[aria-current='page'] { color: var(--brass); }

@media (max-width: 640px) {
  .site-nav { gap: 0.9rem; }
  .brand-tag { display: none; }
}

/* Below ~560px the wordmark and four links cannot share a row. The header
   wraps onto two lines instead of collapsing into a menu button: four
   destinations do not justify hiding navigation behind a tap. */
@media (max-width: 560px) {
  .site-header .wrap {
    flex-wrap: wrap;
    gap: 0.7rem 1rem;
    padding-block: 0.75rem;
  }
  .site-nav {
    width: 100%;
    flex-wrap: wrap;
    gap: 0.6rem 1.1rem;
  }
  .brand svg { width: 24px; }
  .brand-name { font-size: 0.72rem; }
}

/* -- Hero ------------------------------------------------------------------ */

.hero { padding-block: clamp(3rem, 8vw, 7rem) clamp(2.5rem, 5vw, 4rem); }

.hero-grid {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: center;
}

@media (max-width: 900px) {
  .hero-grid { grid-template-columns: 1fr; }
}

.hero-bio {
  margin-top: 1.6rem;
  font-size: var(--fs-lead);
  color: var(--text-dim);
  max-width: 42ch;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.1rem;
  margin-top: 2.2rem;
}

/* -- Artefact card (where a portrait would be) ----------------------------- */

.artefact {
  border: 1px solid var(--line);
  background: var(--ink-raised);
  padding: 1.15rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.artefact-label {
  align-self: flex-start;
  background: var(--brass);
  color: var(--ink-on-brass);
  font-family: var(--font-mono);
  font-size: 0.6563rem;  /* was 9.5px */
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 0.34rem 0.6rem;
}

/* -- Buttons --------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  text-decoration: none;
  min-height: 44px;
  padding: 0.85rem 1.3rem;
  border: 1px solid var(--brass);
  background: var(--brass);
  color: var(--ink-on-brass);
  transition: background var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
  cursor: pointer;
}
.btn:hover { background: transparent; color: var(--brass); }
/* A pressed state, so a tap on a phone acknowledges itself. transform only —
   it is composited, and it cannot shift the layout of anything around it. */
.btn:active { transform: translateY(1px); }

.btn--ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--line);
}
.btn--ghost:hover { border-color: var(--brass); color: var(--brass); }

.section--paper .btn {
  border-color: var(--brass-deep);
  background: var(--brass-deep);
  color: var(--paper);
}
.section--paper .btn:hover { background: transparent; color: var(--brass-deep); }

/* -- Credential ticker ----------------------------------------------------- */

.ticker {
  position: relative;
  display: flex;
  border-block: 1px solid var(--line);
  padding-block: 1rem;
  /* The gutter the pause control sits in. The viewport ends here, so the
     moving text is clipped before it reaches the control rather than sliding
     out from behind it. */
  padding-inline-end: 6.5rem;
}

.ticker-viewport {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
}

.ticker-track {
  display: flex;
  width: max-content;
  white-space: nowrap;
}

.ticker-item {
  flex: none;
  padding-inline: 1.7rem;
  border-inline-end: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: 0.7813rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text);
}

@media (prefers-reduced-motion: no-preference) {
  .ticker-track { animation: ticker-slide 42s linear infinite; }

  /* Hover and focus-within are conveniences. The checkbox is the mechanism —
     the one that exists for someone on a phone, who has neither. */
  .ticker:hover .ticker-track,
  .ticker:focus-within .ticker-track,
  .ticker-pause:checked ~ .ticker-track { animation-play-state: paused; }
}

/* Visually hidden and still focusable: display:none or visibility:hidden
   would take it out of the tab order, which is the whole point of it. The
   same clip-path pattern as .hp rather than opacity: 0 — tests/no-js.spec.ts
   asserts that nothing in main sits at low opacity, and that check is worth
   more than the one line it would cost to exempt this. Clicks reach it
   through the label either way. */
.ticker-pause {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.ticker-pause-label {
  position: absolute;
  inset-inline-end: 0;
  inset-block: 0;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-inline: 0.7rem;
  background: var(--ink);
  border-inline-start: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--steel);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.ticker-pause-label:hover { color: var(--brass); }

/* The focus ring belongs on the label, since the input it belongs to is a
   pixel wide and invisible. */
.ticker-pause:focus-visible + .ticker-pause-label {
  outline: 2px solid var(--brass);
  outline-offset: -2px;
  color: var(--brass);
}

/* Swapping which word is display:none swaps the label's accessible name with
   it, so the control announces what it will do rather than what it did. */
.ticker-pause-word--play { display: none; }
.ticker-pause:checked + .ticker-pause-label .ticker-pause-word--pause { display: none; }
.ticker-pause:checked + .ticker-pause-label .ticker-pause-word--play { display: inline; }
.ticker-pause:checked + .ticker-pause-label { color: var(--brass); }

/* Says what it pauses, for a screen reader reaching it out of context. */
.ticker-pause-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

@keyframes ticker-slide {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Under reduced motion the track wraps into a static list instead of
   scrolling, so the facts stay readable rather than clipped. */
@media (prefers-reduced-motion: reduce) {
  .ticker { padding-inline-end: 0; }
  .ticker-viewport { overflow: visible; mask-image: none; }
  .ticker-track { flex-wrap: wrap; width: auto; white-space: normal; }
  /* Nothing is moving, so a pause control would be a lie. */
  .ticker-pause,
  .ticker-pause-label { display: none; }
  .ticker-item:last-child { border-inline-end: 0; }
  /* The second copy of the track exists only so the marquee can loop
     seamlessly. Once the track stops moving and wraps, it stops being
     off-screen and prints every credential a second time. */
  .ticker-item--dup { display: none; }
}

@media print {
  .ticker { padding-inline-end: 0; }
  .ticker-viewport { overflow: visible; mask-image: none; }
  .ticker-track { flex-wrap: wrap; width: auto; white-space: normal; }
  .ticker-item--dup,
  .ticker-pause-label { display: none; }
}

/* -- Services -------------------------------------------------------------- */

.services {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0 2.5rem;
  margin-top: 2.6rem;
  border-block-start: 1px solid var(--line-paper);
}

.service {
  padding-block: 2rem;
  border-block-end: 1px solid var(--line-paper);
}

.service-n {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.16em;
  color: var(--brass-deep);
  margin-bottom: 0.9rem;
}

.service h3 { margin-bottom: 0.7rem; }
.service p { color: var(--text-ink-dim); font-size: var(--fs-small); }

/* -- Work cards ------------------------------------------------------------ */

.work-grid {
  display: grid;
  gap: 1.1rem;
  grid-template-columns: repeat(auto-fit, minmax(285px, 1fr));
  margin-top: 2.6rem;
}

.work-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  background: var(--ink-raised);
  padding: 1.6rem;
  text-decoration: none;
  color: inherit;
  transition: border-color var(--dur-fast) var(--ease);
}
.work-card:hover { border-color: var(--brass); color: inherit; }
.work-card:active { transform: translateY(1px); }

.work-meta {
  font-family: var(--font-mono);
  font-size: 0.6875rem;  /* was 9.5px, and it carries sector, year and client */
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--steel);
  margin-bottom: 1.5rem;
}

/* The outcome leads the card, not the title — the change is what a prospect
   scans for. */
.work-outcome {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: clamp(1.6rem, 2.3vw, 2rem);
  line-height: 0.92;
  letter-spacing: -0.03em;
  color: var(--brass);
  margin-bottom: 0.45rem;
  text-wrap: balance;
}
.work-outcome-label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;  /* was 10px */
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--steel);
  margin-bottom: 1.2rem;
}

/* Class, not tag: the card's title is an h2 on the work index and an h3 on the
   homepage, because the correct level depends on what sits above it. */
.work-card-title {
  margin-bottom: 0.6rem;
  font-size: 0.9375rem;
  line-height: var(--lh-snug);
  letter-spacing: 0.01em;
  color: var(--text);
}
.work-desc {
  font-size: var(--fs-small);
  color: var(--text-dim);
  margin: 0;
}

.work-more {
  margin-top: auto;
  padding-top: 1.4rem;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--brass);
}

/* -- Statement break ------------------------------------------------------- */

.statement {
  padding-block: clamp(3.5rem, 7vw, 6.5rem);
  background: var(--ink);
  border-block-start: 1px solid var(--line-soft);
}
.statement p {
  font-family: var(--font-display);
  text-transform: uppercase;
  font-size: var(--fs-statement);
  line-height: 0.86;
  letter-spacing: -0.035em;
  max-width: 18ch;
  margin: 0;
  color: var(--text);
}

/* -- Prose ----------------------------------------------------------------- */

.prose { max-width: var(--measure); }
.prose h2 { margin: 2.4rem 0 0.9rem; font-size: 1.5rem; }
.prose h3 { margin: 1.9rem 0 0.7rem; }
.prose ul, .prose ol { padding-inline-start: 1.2em; }
.prose li { margin-bottom: 0.45em; }
.prose blockquote {
  margin: 1.8rem 0;
  padding-inline-start: 1.1rem;
  border-inline-start: 2px solid var(--brass);
  color: var(--text-dim);
}

/* -- Definition rows (CV, outcomes) ---------------------------------------- */

.rows { border-block-start: 1px solid var(--line); }
.row-item {
  display: grid;
  grid-template-columns: minmax(6.5rem, 9rem) 1fr;
  gap: 1.5rem;
  padding-block: 1.5rem;
  border-block-end: 1px solid var(--line);
}
@media (max-width: 640px) {
  .row-item { grid-template-columns: 1fr; gap: 0.5rem; }
}
.row-key {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--steel);
  font-variant-numeric: tabular-nums;
}
.row-val p { margin: 0; }
.row-val h3 { margin-bottom: 0.3rem; }
.row-val .org {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.07em;
  color: var(--brass);
  margin: 0 0 0.8rem;
}
.row-val ul { margin: 0; padding-inline-start: 1.1em; }
.row-val li {
  margin-bottom: 0.4em;
  color: var(--text-dim);
  font-size: var(--fs-small);
}

/* -- Tag list -------------------------------------------------------------- */

.tags { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
.tags li {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-dim);
  border: 1px solid var(--line);
  padding: 0.4rem 0.7rem;
}
.section--paper .tags li { color: var(--text-ink-dim); border-color: var(--line-paper); }

/* -- Form ------------------------------------------------------------------ */

.form { display: grid; gap: 1.2rem; max-width: 34rem; margin-top: 2rem; }
.field { display: grid; gap: 0.45rem; }
.field label {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--steel);
}
.field input,
.field textarea {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  color: var(--text);
  background: var(--ink-raised);
  border: 1px solid var(--line);
  padding: 0.8rem 0.9rem;
  width: 100%;
}
.field input:focus,
.field textarea:focus { border-color: var(--brass); outline: none; }
.field textarea { min-height: 9rem; resize: vertical; }

/* The honeypot. Off-screen rather than display:none, since some bots skip
   hidden fields; it is also aria-hidden and removed from the tab order. */
.hp {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.form-note {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--steel);
}
.form-status[data-state='ok'] { color: var(--ok); }
.form-status[data-state='error'] { color: var(--warn); }

/* -- Footer ---------------------------------------------------------------- */

.site-footer {
  border-block-start: 1px solid var(--line-soft);
  padding-block: 3.5rem;
  color: var(--text-dim);
  font-size: var(--fs-small);
}
.footer-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 2rem;
  justify-content: space-between;
  align-items: flex-start;
}
.site-footer a {
  color: var(--text-dim);
  text-decoration: none;
  /* Stacked by a <br>, these measured 15px tall with 23px between centres:
     too small for 2.5.8 and too close together for its spacing exception,
     which is the combination that actually makes a link hard to hit. */
  display: inline-block;
  padding-block: 0.3rem;
}
.site-footer a:hover { color: var(--brass); }
.footer-legal {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--steel);
}

/* -- Motion (Tier 1) ------------------------------------------------------- */
/*
   Content is visible by default. The inline head script sets
   data-motion="on" only when the capability gate passes, and ONLY THEN does
   the hidden state below apply. If the script never runs, is blocked, or the
   device fails the gate, every element renders in its final state — a
   complete page rather than a white one. tests/no-js.spec.ts locks this.

   The hero headline is deliberately absent: it is the LCP element, and fading
   it in both delays LCP and looks broken on a slow connection.
*/

[data-motion='on'] .reveal {
  opacity: 0;
  transform: translateY(14px);
}

[data-motion='on'] .reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur-slow) var(--ease),
    transform var(--dur-slow) var(--ease);
}

[data-motion='on'] .reveal-stagger > * {
  opacity: 0;
  transform: translateY(12px);
}
[data-motion='on'] .reveal-stagger.is-in > * {
  opacity: 1;
  transform: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}

@media (prefers-reduced-motion: reduce) {
  [data-motion='on'] .reveal,
  [data-motion='on'] .reveal-stagger > * {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
