/* =========================================================
   Carlo Figueroa — portfolio
   Direction: "field notes" — a data engineer's lab notebook.
   Deep ink ground, warm paper accents, one amber highlight
   used exactly the way Carlo uses red/amber in his own
   dashboards: to say "look here, this is the one number
   that matters." Everything else stays quiet on purpose.
   ========================================================= */

:root {
  --ink: #14171c;
  --ink-2: #1b1f26;
  --ink-3: #232833;
  --line: #333a46;
  --paper: #f4efe4;
  --paper-dim: #d9d2c0;
  --fg: #ece7da;
  --fg-muted: #9a9484;
  --fg-faint: #948e83;
  --amber: #e8a33d;
  --amber-dim: #a8763a;
  --teal: #5fb3a3;

  /* Role-split tokens. --amber carries TEXT and has to clear 4.5:1;
     --amber-cta is the button FILL and only owes 3:1 to its neighbours.
     One token could not do both once light mode was measured, so they
     are separate. In dark they resolve to the same colours as before —
     this theme was already passing and should not move. */
  --amber-cta: var(--amber);
  --amber-cta-hover: #f2b559;
  --cta-label: #14171c;

  --nav-bg: rgba(20, 23, 28, 0.86);
  --vignette-0: rgba(20, 23, 28, 0);

  --font-display: "Fraunces", "Iowan Old Style", Georgia, serif;
  --font-body: "Archivo", -apple-system, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;

  --wrap: 1180px;
  --rail-w: clamp(196px, 17vw, 236px);
  --gap: clamp(1.25rem, 3vw, 2.5rem);
}

/* "Togepi" light theme — cream shell, warm brown ink, same gold accent
   (the thread linking both themes: Umbreon's rings / Togekiss's crest),
   one sparing coral nod to Togepi's spikes for the "live" status only. */
:root[data-theme="light"] {
  --ink: #f7ecd6;
  --ink-2: #fbf5e9;
  --ink-3: #f1e3c4;
  --line: #e2d0a6;
  --paper: #2a2118;
  --paper-dim: #4a3c2a;
  --fg: #3a2f22;
  --fg-muted: #6b5c46;
  --fg-faint: #71644c;
  --amber: #8c5b14;
  --amber-dim: #8f5e15;
  --teal: #a44c35;

  /* The gold fill stays exactly as approved (#b9781a). Darkening it far
     enough for a cream label would have turned the button muted brown;
     flipping the label to near-black instead clears 4.93:1 and keeps the
     colour. That leaves the fill 2.87:1 against the page — just under the
     3:1 boundary rule — so the CTA carries a 1px --amber-dim border,
     which measures 4.37:1 and reads as almost nothing. */
  --amber-cta: #b9781a;
  --amber-cta-hover: #c8891f;
  --cta-label: #14171c;

  --nav-bg: rgba(247, 236, 214, 0.86);
  --vignette-0: rgba(247, 236, 214, 0);
}

/* Cross-document view transitions: a native, zero-JS cross-fade between
   pages on the same origin (index -> case study, case study -> case
   study). Chromium supports it today; Safari/Firefox fall back to a
   normal instant navigation with no error and no visual glitch — this
   is a pure progressive enhancement, safe to ship as-is. */
@view-transition {
  navigation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.32s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 64px 64px;
  background-position: -1px -1px;
  background-attachment: fixed;
}

/* subtle vignette so the grid doesn't fight the content */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(ellipse 80% 60% at 50% 0%, var(--vignette-0) 0%, var(--ink) 78%);
  z-index: 0;
}

a { color: inherit; }
img { max-width: 100%; display: block; }

.wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 var(--gap);
  position: relative;
  z-index: 1;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 0.5em;
  color: var(--paper);
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--amber);
  display: flex;
  align-items: center;
  gap: 0.6em;
  margin-bottom: 1em;
}
.eyebrow::before {
  content: "";
  width: 22px;
  height: 1px;
  background: var(--amber);
  display: inline-block;
}

/* ---------------- nav ---------------- */
.site-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--nav-bg);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.site-nav .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 68px;
}
.brand {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  color: var(--paper);
  text-decoration: none;
  white-space: nowrap;
}
/* Not a flex row any more. The old wordmark was three separate items
   ("figueroa", the dot, "data") held apart by a 0.4em gap; "carlofigs
   data" is one word plus a space, so that gap would wrongly split it
   into "carlo figs data". Plain inline text puts the space where it
   belongs and keeps "figs" tight against "carlo". */
.brand .hi { color: var(--amber); }
.nav-links {
  display: flex;
  gap: clamp(1rem, 2.4vw, 2.2rem);
  list-style: none;
  margin: 0; padding: 0;
  font-size: 0.92rem;
}
.nav-links a {
  text-decoration: none;
  color: var(--fg-muted);
  transition: color 0.2s ease;
  position: relative;
}
.nav-links a:hover { color: var(--amber); }
/* Scoped to .site-nav, not .nav-links: the CTA moved into .nav-foot so
   the rail can pin it to the bottom, and the bar keeps it on the right. */
.site-nav a.cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  text-decoration: none;
  color: var(--cta-label);
  background: var(--amber-cta);
  border: 1px solid var(--amber-dim);
  padding: 0.5em 1em;
  border-radius: 2px;
  font-family: var(--font-mono);
  font-size: 0.82rem;
}
.site-nav a.cta:hover { background: var(--amber-cta-hover); color: var(--cta-label); }
/* Sized to match the LinkedIn/GitHub marks beneath it, so the three
   read as one set with email as the primary, filled one. */
.site-nav a.cta svg { width: 14px; height: 14px; flex-shrink: 0; }

/* ---------------- hero ---------------- */
.hero {
  padding: clamp(4rem, 10vw, 7.5rem) 0 clamp(3rem, 6vw, 5rem);
}
.hero h1 {
  font-size: clamp(2.6rem, 6vw, 4.4rem);
  line-height: 1.04;
  max-width: 15ch;
}
.hero h1 em {
  font-style: italic;
  color: var(--amber);
}
.hero .lede {
  max-width: 56ch;
  color: var(--fg-muted);
  font-size: clamp(1.05rem, 1.6vw, 1.25rem);
  margin: 1.2em 0 2.2em;
}
.hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 0;
}
.pill {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--fg-muted);
  border: 1px solid var(--line);
  padding: 0.4em 0.9em;
  border-radius: 999px;
  margin-right: 0.6em;
  white-space: nowrap;
}
.pill.on { color: var(--teal); border-color: color-mix(in srgb, var(--teal) 40%, transparent); }

/* ---------------- theme toggle ---------------- */
.theme-toggle {
  appearance: none;
  background: none;
  border: 1px solid var(--line);
  color: var(--fg-muted);
  width: 36px; height: 36px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 0.95rem;
  transition: border-color 0.2s ease, color 0.2s ease;
}
.theme-toggle:hover { border-color: var(--amber-dim); color: var(--amber); }
.theme-toggle .i-light { display: none; }
:root[data-theme="light"] .theme-toggle .i-dark { display: none; }
:root[data-theme="light"] .theme-toggle .i-light { display: inline; }

/* ---------------- section shell ---------------- */
section { padding: clamp(3rem, 7vw, 5.5rem) 0; }
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  border-bottom: 1px solid var(--line);
  padding-bottom: 1.2rem;
  margin-bottom: 2.4rem;
}
.section-head h2 { font-size: clamp(1.6rem, 3vw, 2.1rem); margin: 0; }
.section-head .count {
  font-family: var(--font-mono);
  color: var(--fg-faint);
  font-size: 0.85rem;
}

/* ---------------- case study cards ---------------- */
.cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gap); }
@media (max-width: 820px) { .cards { grid-template-columns: 1fr; } }

.card {
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: clamp(1.6rem, 3vw, 2.4rem);
  text-decoration: none;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
  transition: transform 0.25s ease, border-color 0.25s ease;
}
.card:nth-child(2) { transform: translateY(1.4rem); }
@media (max-width: 820px) { .card:nth-child(2) { transform: none; } }
.card:hover { transform: translateY(-4px); border-color: var(--amber-dim); }
.card:nth-child(2):hover { transform: translateY(1.4rem) translateY(-4px); }
.card-badge {
  width: 56px;
  height: 56px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--ink-3);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1.3rem;
  transition: border-color 0.25s ease;
}
.card:hover .card-badge { border-color: var(--amber-dim); }
.card-badge svg { width: 30px; height: 30px; }
.card-badge .line { stroke: var(--line); fill: none; }
.card-badge .dot { fill: var(--fg-muted); stroke: none; }
.card-badge .accent { stroke: var(--amber); fill: none; }
.card-badge .accent-dot { fill: var(--amber); stroke: none; }

.card .num {
  font-family: var(--font-mono);
  color: var(--fg-faint);
  font-size: 0.8rem;
  margin-bottom: 1.2rem;
}
.card h3 {
  font-size: 1.6rem;
  margin-bottom: 0.5rem;
}
.card p { color: var(--fg-muted); margin: 0 0 1.4rem; flex-grow: 1; }
.card .tags { display: flex; flex-wrap: wrap; gap: 0.5em; margin-bottom: 1.2rem; }
.tag {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--fg-muted);
  background: var(--ink-3);
  border: 1px solid var(--line);
  padding: 0.3em 0.7em;
  border-radius: 3px;
}
.card .read {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--amber);
  display: flex;
  align-items: center;
  gap: 0.5em;
}
.card .read .arrow { transition: transform 0.2s ease; }
.card:hover .read .arrow { transform: translateX(4px); }

/* ---------------- stack strip ---------------- */
.stack-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
}
.stack-strip .tag { font-size: 0.8rem; padding: 0.5em 1em; }

/* ---------------- about / bio ---------------- */
.bio { display: grid; grid-template-columns: 1.1fr 0.9fr; gap: var(--gap); align-items: start; }
@media (max-width: 820px) { .bio { grid-template-columns: 1fr; } }
.bio p { color: var(--fg-muted); max-width: 62ch; }
.bio p + p { margin-top: 1.1em; }
.bio strong { color: var(--paper); font-weight: 600; }
.fact-list { list-style: none; margin: 0; padding: 0; font-family: var(--font-mono); font-size: 0.85rem; }
.fact-list li {
  display: flex;
  justify-content: space-between;
  padding: 0.85em 0;
  border-bottom: 1px solid var(--line);
  color: var(--fg-muted);
  gap: 1rem;
}
.fact-list li span:last-child { color: var(--fg); text-align: right; font-variant-numeric: tabular-nums; }

/* ---------------- footer ---------------- */
footer {
  border-top: 1px solid var(--line);
  padding: 2.6rem 0 3.4rem;
  margin-top: 2rem;
}
footer .wrap { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1.5rem; align-items: center; }
/* Wraps because adding LinkedIn and GitHub took this row to five items:
   measured at 485px of content against the 335px a 375px phone
   actually offers. It was nowrap and would have overflowed. */
footer .links { display: flex; flex-wrap: wrap; gap: 0.7rem 1.6rem; font-family: var(--font-mono); font-size: 0.85rem; }
footer .links a { text-decoration: none; color: var(--fg-muted); }
footer .links a:hover { color: var(--amber); }

/* ---------------- case study page ---------------- */
.cs-header { padding: clamp(3rem, 8vw, 5.5rem) 0 2.5rem; border-bottom: 1px solid var(--line); }
.cs-header .back {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--fg-faint);
  text-decoration: none;
  display: inline-flex;
  gap: 0.5em;
  margin-bottom: 2rem;
}
.cs-header .back:hover { color: var(--amber); }
.cs-header h1 { font-size: clamp(2.2rem, 5vw, 3.4rem); max-width: 22ch; }
.cs-header .dek { color: var(--fg-muted); max-width: 60ch; font-size: 1.15rem; margin-top: 0.6em; }

.cs-meta {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--gap);
  margin-top: 2.6rem;
}
@media (max-width: 720px) { .cs-meta { grid-template-columns: repeat(2, 1fr); } }
.cs-meta div { border-top: 2px solid var(--line); padding-top: 0.7em; }
.cs-meta .k { font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--fg-faint); }
.cs-meta .v { font-size: 1.02rem; color: var(--paper); margin-top: 0.3em; font-variant-numeric: tabular-nums; }

.cs-body { max-width: 74ch; }
.cs-body h2 {
  font-size: 1.5rem;
  margin-top: 3rem;
  display: flex;
  align-items: center;
  gap: 0.7em;
}
.cs-body h2 .step {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--amber);
  border: 1px solid var(--amber-dim);
  width: 2em; height: 2em;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.cs-body p { color: var(--fg-muted); }
.cs-body ul { color: var(--fg-muted); padding-left: 1.3em; }
.cs-body li { margin-bottom: 0.5em; }
.cs-body strong { color: var(--paper); font-weight: 600; }

.callout {
  background: var(--ink-2);
  border-left: 3px solid var(--amber);
  padding: 1.3em 1.6em;
  margin: 1.8em 0;
  font-size: 0.98rem;
  color: var(--fg);
  border-radius: 0 3px 3px 0;
}
.callout .k { font-family: var(--font-mono); color: var(--amber); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; display: block; margin-bottom: 0.4em; }

/* Outcome figures.
   This was three equal boxes in a row — the textbook evenly-spaced
   three-up, where every number shouts at the same volume and the reader
   has to work out which one is the finding. It isn't a three-way tie:
   one figure IS the result and the other two corroborate it. So the
   lead figure gets the room and the supporting pair sits underneath it,
   quieter, in the same card.

   Every figure is tabular-nums. On a data portfolio, numbers that sit
   on a consistent grid read as a readout; proportional digits read as
   decoration. */
.outcome {
  margin: 2.4rem 0;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  overflow: hidden;
}
.outcome-lead {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem 1.4rem;
  padding: 1.7em 1.6em 1.5em;
}
.outcome-lead .n {
  font-family: var(--font-display);
  font-size: clamp(2.8rem, 6vw, 4.2rem);
  line-height: 0.9;
  color: var(--amber);
  font-variant-numeric: tabular-nums;
}
.outcome-lead .l {
  margin: 0;
  color: var(--fg);
  font-size: 1.02rem;
  max-width: 30ch;
}
.outcome-support {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-top: 1px solid var(--line);
}
.outcome-support .sub { padding: 1.15em 1.6em; }
.outcome-support .sub + .sub { border-left: 1px solid var(--line); }
.outcome-support .n {
  display: block;
  font-family: var(--font-display);
  font-size: 1.55rem;
  line-height: 1;
  color: var(--paper);
  font-variant-numeric: tabular-nums;
}
.outcome-support .l {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--fg-muted);
  margin-top: 0.55em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.figure {
  border: 1px dashed var(--line);
  border-radius: 4px;
  padding: 2.4em;
  text-align: center;
  margin: 2em 0;
  background: var(--ink-2);
}
.figure img { border-radius: 3px; margin: 0 auto; }
.figure .cap {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--fg-faint);
  margin-top: 1em;
}
.figure.placeholder {
  color: var(--fg-faint);
  font-family: var(--font-mono);
  font-size: 0.82rem;
}

/* live-viz embed (falls back to a static image — see HANDOFF.md) */
.figure.viz-embed { padding: 0; overflow: hidden; }
.viz-container { width: 100%; min-height: 500px; background: var(--ink-3); }
/* A static export sizes itself; the 500px floor is only there to hold
   space for a live viz while it loads. */
.viz-container.viz-static { min-height: 0; }
.viz-container img { width: 100%; display: block; border-radius: 0; margin: 0; }
.viz-embed .cap { padding: 1em 1.6em; text-align: left; border-top: 1px solid var(--line); margin-top: 0; }
.viz-embed .cap #vizFallbackNote { color: var(--fg-faint); }

.tech-list { display: flex; flex-wrap: wrap; gap: 0.5em; margin: 1.6em 0 0; }

.cs-nav-out {
  border-top: 1px solid var(--line);
  padding: 2.6rem 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
}
.cs-nav-out a {
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.88rem;
  color: var(--fg-muted);
}
.cs-nav-out a:hover { color: var(--amber); }

/* ---------------- reveal-on-load ---------------- */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translateY(14px);
    animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
  }
  .r1 { animation-delay: 0.05s; }
  .r2 { animation-delay: 0.16s; }
  .r3 { animation-delay: 0.27s; }
  .r4 { animation-delay: 0.38s; }
  @keyframes rise {
    to { opacity: 1; transform: translateY(0); }
  }
}

/* =========================================================
   Narrow viewports
   The prototype was QA'd at 1440x900 only. Everything below is
   additive and scoped to a max-width, so >=901px renders exactly
   as it did before this block existed.
   ========================================================= */

.figure-scroll { margin: 0; }
.scroll-hint { display: none; }

@media (max-width: 720px) {
  /* The lineage SVG is authored at 1420px. Scaled into a phone's
     content column it lands near 250px, which renders its 13px labels
     at about 2px — technically present, actually unreadable. It gets
     its own scroll pane at a legible width instead of shrinking. */
  .figure-scroll {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
  }
  .figure-scroll img { min-width: 620px; }
  .scroll-hint { display: inline; }

  /* 2.4em of padding eats ~80px of a 335px content column. */
  .figure { padding: 1.2em; }

  /* A fixed background repaints the whole grid on every scroll frame,
     which mobile GPUs feel; several mobile browsers ignore the value
     anyway. The grid itself is unchanged. */
  body { background-attachment: scroll; }

  /* 500px of empty box is a lot of phone. */
  .viz-container { min-height: 360px; overflow-x: auto; }
}

@media (max-width: 560px) {
  /* The nav needs ~440px for brand + toggle + Work + About + CTA, which
     is more than any phone has. Work and About are same-page anchors to
     content sitting directly below the fold, so they drop and the
     brand, theme toggle and CTA keep the row. */
  .nav-anchor { display: none; }
  .theme-toggle { width: 40px; height: 40px; }

  .section-head { flex-wrap: wrap; gap: 0.4rem 1.5rem; }

  /* space-between crushes the label against long values such as
     "Endeavour Group — Loyalty & Personalisation". */
  .fact-list li { flex-direction: column; gap: 0.15em; }
  .fact-list li span:last-child { text-align: left; }
}

@media (max-width: 520px) {
  /* The supporting pair stops fitting side by side. */
  .outcome-support { grid-template-columns: 1fr; }
  .outcome-support .sub + .sub { border-left: none; border-top: 1px solid var(--line); }
}

@media (max-width: 480px) {
  /* clamp() floors the headline at 2.6rem, which stacks it about nine
     lines deep at 375px. */
  .hero h1 { font-size: 2.1rem; }
}

/* =========================================================
   Shell — vertical rail at >=901px, horizontal bar below
   One set of markup, two layouts. The rail is the primary
   form; the bar is what it collapses to on a phone, which is
   the layout the responsive block above already tuned.
   ========================================================= */

.nav-group { display: none; }          /* rail-only; the bar has no room */
.nav-foot {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.4vw, 2.2rem);
}
.nav-label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-faint);
}
.nav-work, .nav-steps {
  list-style: none;
  margin: 0 0 1.6rem;
  padding: 0;
  font-size: 0.92rem;
}
.nav-work a, .nav-steps a {
  text-decoration: none;
  color: var(--fg-muted);
  transition: color 0.2s ease;
}
.nav-work a:hover, .nav-steps a:hover { color: var(--amber); }
.site-nav .n {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--fg-faint);
  flex-shrink: 0;
}

@media (min-width: 901px) {
  /* The rail takes its width out of the page, so the content column
     retunes to keep its optical centre in what's left rather than
     hugging the rail. */
  :root { --wrap: 1020px; }
  body { padding-left: var(--rail-w); }

  .site-nav {
    position: fixed;
    top: 0; left: 0; bottom: 0;
    width: var(--rail-w);
    border-bottom: none;
    border-right: 1px solid var(--line);
  }
  .site-nav .wrap {
    max-width: none;
    height: 100%;
    padding: 2rem 1.4rem;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 2.2rem;
    overflow-y: auto;
  }

  .nav-group { display: block; }
  .nav-links { flex-direction: column; gap: 0; }
  .nav-foot { flex-direction: column; align-items: stretch; gap: 0.9rem; margin-top: auto; }
  .nav-foot .theme-toggle { align-self: flex-start; }
  .nav-foot a.cta { text-align: center; }

  /* The rail's left rule is where amber earns its keep: it stops being
     decoration and becomes the "you are here" marker. */
  .site-nav li a {
    display: flex;
    align-items: baseline;
    gap: 0.65em;
    padding: 0.44em 0 0.44em 0.85em;
    border-left: 1px solid var(--line);
    transition: color 0.2s ease, border-color 0.2s ease;
  }
  .site-nav li a:hover { border-left-color: var(--amber-dim); }
  .site-nav li a.is-active { border-left-color: var(--amber); color: var(--amber); }
  .site-nav li a.is-active .n { color: var(--amber); }
  .nav-foot a.cta { border-left: none; display: flex; padding: 0.6em 1em; }
}

@media (max-width: 900px) {
  /* Back to the bar. Case-study steps and the cross-links are dropped
     rather than crammed — the page they point at is directly below. */
  .nav-group { display: none; }
}

/* Anchor jumps to the case-study headings must clear the collapsed top
   bar; at >=901px the rail is beside the content so this costs nothing. */
.cs-body h2[id] { scroll-margin-top: 90px; }

/* =========================================================
   Surface texture
   Every surface here was a flat CSS fill, which is the single
   most "generated" thing about the page — real printed matter
   has tooth. A tiled SVG fractal-noise tile gives the card
   surfaces a grain so they read as stock rather than as a
   rectangle of hex.

   Applied as a BACKGROUND layer, not an overlay pseudo-element,
   so it can never sit on top of text and needs no stacking
   games. The exception is the hero, which has no surface of its
   own; it gets a masked pseudo-element that fades out down the
   page, and .wrap already carries z-index 1 so content stays
   above it.

   The tile is a data URI, so this adds no request. It is one
   140px tile the browser rasterises once and repeats.
   ========================================================= */

:root {
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.42'/%3E%3C/svg%3E");
}

.card,
.outcome,
.callout,
.figure {
  background-image: var(--grain);
  background-size: 140px 140px;
  background-blend-mode: overlay;
}

.hero { position: relative; }
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.5;
  background-image: var(--grain);
  background-size: 140px 140px;
  /* fade the grain out before it reaches the next section, so the hero
     has tooth without the whole page looking dusty */
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 88%);
  mask-image: linear-gradient(to bottom, #000 0%, transparent 88%);
}

/* =========================================================
   Portrait
   Sits in the About column above the fact list. Framed in the
   same treatment as the cards so it reads as part of the page
   rather than a headshot dropped into a template.
   ========================================================= */

.bio-side { display: flex; flex-direction: column; gap: 1.6rem; }

.portrait {
  margin: 0;
  background-color: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  overflow: hidden;
  isolation: isolate;
  background-image: var(--grain);
  background-size: 140px 140px;
  background-blend-mode: overlay;
}
.portrait img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;   /* so any crop of a real photo still fills the frame */
  display: block;
}

/* BACKUP TREATMENT — duotone, kept so it can be compared against the
   plain framed photo without rebuilding anything. To try it, add the
   class in index.html:

       <figure class="portrait portrait--duotone">

   The photo drops to luminosity over an amber ground, so it tints with
   whichever theme is active instead of being a fixed second asset.

   Note it will only read as a duotone once a REAL photo is in place —
   against the mostly-transparent placeholder it just shows a flat amber
   plate, which is the mechanism working, not a bug.

   Delete this block once the choice is settled. */
.portrait--duotone { background-color: var(--amber); }
.portrait--duotone img {
  filter: grayscale(1) contrast(1.06);
  mix-blend-mode: luminosity;
}

@media (max-width: 820px) {
  /* The About column is full width once the grid collapses, so an
     unconstrained portrait would tower over the copy. */
  .portrait { max-width: 320px; }
}

/* Empty state for the dashboard fallback, used only while the static
   export doesn't exist yet. Replaces the browser's broken-image glyph. */
.viz-container.viz-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

/* =========================================================
   Social links
   Secondary to the CTA by design: the email button is the one
   action worth taking, these are for checking someone out.
   Outline rather than filled, so they read as a step down
   rather than three buttons competing.
   ========================================================= */

/* Rail only. The collapsed bar is already at its width budget at 375px
   — brand + toggle + CTA needs the whole row, which is why Work/About
   drop at 560 — so two more controls would put the overflow straight
   back. They live in the footer instead, which is on every page and
   reachable at every width. */
.nav-social { display: none; }

@media (min-width: 901px) {
  .nav-social {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
  }
  .nav-social a {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.45em;
    padding: 0.55em 0.4em;
    text-decoration: none;
    border: 1px solid var(--line);
    border-radius: 2px;
    font-family: var(--font-mono);
    font-size: 0.71rem;
    color: var(--fg-muted);
    transition: color 0.2s ease, border-color 0.2s ease;
  }
  .nav-social a:hover { color: var(--amber); border-color: var(--amber-dim); }
  .nav-social svg { width: 13px; height: 13px; flex-shrink: 0; }
}

/* "Open the interactive dashboard" link in the figure caption. */
.viz-open {
  display: inline-block;
  margin-top: 0.5em;
  color: var(--amber);
  text-decoration: none;
  border-bottom: 1px solid var(--amber-dim);
  padding-bottom: 1px;
}
.viz-open:hover { border-bottom-color: var(--amber); }
