/* ============================================================
   Sara AlShimi, Portfolio
   Restrained editorial. Warm paper ground, generous air, one
   accent. The work is the only loud thing on the page.

   Light is the default. Dark is an override, not the other way
   round. Every colour is a token so themes cost nothing.
   ============================================================ */

:root {
  /* Warm paper, never white. Product screenshots are white, and
     they need something to sit against rather than bleed into. */
  --ink-900: #f7f5f0;   /* page */
  --ink-800: #ffffff;   /* raised surface */
  --ink-700: #e4dfd4;   /* hairlines */
  --ink-600: #d4cec0;   /* stronger borders */
  --ink-500: #aca496;   /* diagram strokes */

  --paper: #171614;          /* primary text */
  --paper-dim: #54514a;      /* secondary text */
  --paper-faint: #6a665e;    /* captions and eyebrows. Small text, so it is
                                held above 4.5:1 on the darkest band, not
                                just the lightest. */

  /* Teal, not clay. Red and orange read as error states, which is
     confusing next to a product that uses red for deductions. */
  --accent: #17635c;
  --accent-soft: rgba(23, 99, 92, 0.10);

  /* Section bands. Two neighbouring warm greys alternate down the
     page, close enough in value that the rhythm registers without
     the page looking striped. */
  --band-a: #f7f5f0;
  --band-b: #efebe2;
  --band-cta: #e4e9e5;       /* next case study, faintly toward the accent */
  --footer-bg: #1b201e;      /* the page finally lands on something solid */
  --footer-fg: #eceae4;
  --footer-dim: #a5a89f;
  --footer-line: #333937;

  --font-sans: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
  --font-display: "Instrument Serif", Georgia, serif;
  /* Archivo carries a width axis, so display type can be stretched to fill
     a measure exactly rather than being letter-spaced into shape.
     The mono is the whole technical register: labels, indices, the clock. */
  --font-mega: "Archivo", var(--font-sans);
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, monospace;

  /* Spacing scale */
  --s-1: 0.5rem;
  --s-2: 1rem;
  --s-3: 1.5rem;
  --s-4: 2.5rem;
  --s-5: 4rem;
  --s-6: 6rem;
  --s-7: 9rem;
  --s-8: 13rem;

  --gutter: clamp(1.25rem, 5vw, 4rem);
  --measure: 60ch;
  --max: 1240px;

  /* Motion. Entrances decelerate, exits accelerate, transitions do both.
     Durations are capped: anything a user triggers has to land inside
     300ms or it reads as lag rather than polish. */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);   /* ease-out, the default */
  --ease-in: cubic-bezier(0.4, 0, 1, 1);
  --ease-inout: cubic-bezier(0.65, 0, 0.35, 1);

  --t-press: 150ms;   /* hover and press feedback */
  --t-state: 220ms;   /* small state changes */
  --t-enter: 280ms;   /* entrances, hard ceiling under 300 */
  --stagger: 40ms;    /* per item, kept under the 50ms threshold */
}

/* ---------- Dark theme ----------
   Override only. Nothing below this block knows which theme is running. */
[data-theme="dark"] {
  /* Cool near black rather than warm. Warm greys read as paper; this
     direction wants the page to read as a screen. */
  --ink-900: #08090a;
  --ink-800: #0e1011;
  --ink-700: #191c1e;
  --ink-600: #262a2d;
  --ink-500: #3b4145;

  --paper: #f2f4f4;
  --paper-dim: #a2abad;
  --paper-faint: #787f82;  /* clears 4.5:1 on every band below */

  /* Electric teal. Bright enough to read as emitted light on black,
     still not red, which the product already uses for deductions. */
  --accent: #45e0c8;
  --accent-soft: rgba(69, 224, 200, 0.12);

  --band-a: #08090a;
  --band-b: #0d0f10;
  --band-cta: #0a1413;
  --footer-bg: #050607;
  --footer-fg: #f2f4f4;
  --footer-dim: #a2abad;
  --footer-line: #1c2022;
}
[data-theme="dark"] body::after { opacity: 0.028; mix-blend-mode: normal; }
[data-theme="dark"] .status-dot {
  background: #6fbf73;
  box-shadow: 0 0 0 3px rgba(111, 191, 115, 0.16);
}

/* Grain has to subtract light on paper, not add it */
body::after { mix-blend-mode: multiply; }
.status-dot { background: #3f8f4a; box-shadow: 0 0 0 3px rgba(63, 143, 74, 0.15); }

/* Theme toggle */
.theme-toggle {
  appearance: none;
  background: transparent;
  border: 1px solid var(--ink-600);
  color: var(--paper-dim);
  border-radius: 999px;
  padding: 0.3rem 0.75rem;
  font: inherit;
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  transition: border-color var(--t-press) var(--ease), color var(--t-press) var(--ease);
}
.theme-toggle:hover { border-color: var(--accent); color: var(--accent); }
.theme-toggle .t-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  border: 1px solid currentColor;
  background: linear-gradient(90deg, currentColor 0 50%, transparent 50% 100%);
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--ink-900);
  color: var(--paper);
  font-family: var(--font-sans);
  font-size: clamp(1rem, 0.95rem + 0.2vw, 1.0625rem);
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;

  /* Both families are variable, so let the browser pick the real weight and
     the size-appropriate outline. font-synthesis: none makes a missing weight
     fail visibly rather than being faked into a smeared faux bold. */
  font-synthesis: none;
  font-optical-sizing: auto;
  text-wrap: pretty;          /* fewer orphans in body copy */
}

/* Headings break on meaning, not on whatever fits */
.display, .h2, .h3, .work-title, .user-card h4, blockquote { text-wrap: balance; }

/* Figures are data. They should line up in a column and never reflow
   as digits change width. */
.stat-num, .car-count, .cs-meta-list, .stat-label { font-variant-numeric: tabular-nums; }

::selection { background: var(--accent); color: var(--ink-900); }
img, video { display: block; max-width: 100%; height: auto; }
h1, h2, h3, h4, p, ul, ol, figure { margin: 0; }
ul { padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }

/* Subtle film grain — gives the dark ground texture instead of flat void */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9999;
  opacity: 0.028;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- Layout primitives ---------- */
/* Full width element, centred content. Padding absorbs the centring,
   so any .wrap can carry a background band edge to edge while its
   content stays on the same measure as everything else. */
.wrap {
  width: 100%;
  padding-inline: max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter)));
}

/* ---------- Section bands ----------
   Two neighbouring warm greys alternating down the page. Blocks that
   belong together share a band so they read as one idea. */
.band-a   { background: var(--band-a); }
.band-b   { background: var(--band-b); }
.band-cta { background: var(--band-cta); }

/* A band paints its own background, so it cannot carry outside margin:
   the gap between two bands shows the page colour through the seam as a
   stripe. All band spacing has to be padding. flow-root stops a child's
   top margin collapsing out through the band and doing the same thing. */
.band-a, .band-b, .band-cta {
  margin-block: 0;
  display: flow-root;
}
figure.band-a, figure.band-b, figure.band-cta {
  padding-block: clamp(2.5rem, 6vw, 5rem);
}
/* .pullquote sets its own margin-block later in this file and would win on
   equal specificity, so it has to be zeroed at a higher one. */
.pullquote.band-a, .pullquote.band-b, .pullquote.band-cta {
  margin-block: 0;
  padding-block: clamp(3.5rem, 8vw, 6rem);
  border-bottom: 0;
}
.narrow { max-width: 78ch; }
.prose { max-width: var(--measure); }

.bleed {
  width: 100%;
  max-width: none;
  padding-inline: 0;
}

/* ---------- Type ---------- */
.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-faint);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

.display {
  font-family: var(--font-mega);
  font-variation-settings: "wdth" 96;
  font-weight: 500;
  font-size: clamp(2.5rem, 1.2rem + 6vw, 6.5rem);
  line-height: 0.94;
  letter-spacing: -0.04em;
}

.h2 {
  font-family: var(--font-mega);
  font-variation-settings: "wdth" 98;
  font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.4rem);
  line-height: 1.08;
  letter-spacing: -0.03em;
  font-weight: 500;
}

.h3 {
  font-size: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
  line-height: 1.3;
  letter-spacing: -0.015em;
  font-weight: 500;
}

.lede {
  font-size: clamp(1.125rem, 1rem + 0.7vw, 1.5rem);
  line-height: 1.45;
  letter-spacing: -0.015em;
  color: var(--paper);
  font-weight: 400;
}

.dim { color: var(--paper-dim); }
.serif-em {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  letter-spacing: 0;
}

p + p { margin-top: 1.1em; }

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  background: color-mix(in srgb, var(--ink-900) 78%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t-state) var(--ease);
}
.site-header.is-scrolled { border-bottom-color: var(--ink-700); }
.site-header .wrap {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2rem);
  height: 68px;
}
.site-header .nav { margin-left: auto; }
.wordmark {
  font-weight: 500;
  letter-spacing: -0.02em;
  font-size: 0.95rem;
}
.wordmark span { color: var(--paper-dim); font-weight: 400; }
.nav { display: flex; gap: clamp(1rem, 3vw, 2rem); font-size: 0.875rem; }
.nav a {
  color: var(--paper-dim);
  transition: color var(--t-press) var(--ease);
  position: relative;
}
.nav a:hover, .nav a[aria-current="page"] { color: var(--paper); }

/* Scroll progress rule — thin, only on case studies */
.progress {
  position: absolute;
  left: 0; bottom: -1px;
  height: 1px;
  background: var(--accent);
  width: 0%;
  transition: width 0.1s linear;
}

/* ---------- Home hero ---------- */
.hero {
  padding-block: clamp(5rem, 14vh, 10rem) clamp(4rem, 10vh, 7rem);
}
.hero .display { max-width: 18ch; }
.hero-sub {
  margin-top: var(--s-3);
  max-width: 46ch;
  color: var(--paper-dim);
  font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.25rem);
  line-height: 1.5;
}
.hero-meta {
  margin-top: var(--s-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-3);
  align-items: baseline;
}
.status-dot {
  display: inline-block;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #6fbf73;
  margin-right: 0.55em;
  vertical-align: middle;
  box-shadow: 0 0 0 3px rgba(111,191,115,0.16);
}

/* ---------- Work index ---------- */
.section-rule {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  padding-bottom: var(--s-2);
  border-bottom: 1px solid var(--ink-700);
  margin-bottom: var(--s-5);
}

.work-list { display: grid; gap: clamp(4rem, 9vw, 8rem); }

.work-item { display: block; }
.work-thumb {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  transition: transform var(--t-press) var(--ease);
}
.work-item:hover .work-thumb { transform: translateY(-6px); }
.work-thumb img { width: 100%; height: 100%; object-fit: cover; }

/* ---------- Card mockup ----------
   Two devices instead of one cropped screenshot, because the project
   genuinely spans a web dashboard and an Arabic mobile app and the card
   should say so before anyone clicks.

   Deliberately not using drop shadows: the phone separates from the
   browser behind it with a hard ring in the stage colour, which reads as
   a cut out rather than as chrome stuck onto a screenshot. */
.mock {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background:
    radial-gradient(120% 92% at 50% 118%, var(--accent-soft) 0%, transparent 60%),
    var(--band-a);
  perspective: 1400px;
}
.band-b .mock { background: radial-gradient(120% 92% at 50% 118%, var(--accent-soft) 0%, transparent 60%), var(--band-b); }

.mock-browser {
  position: absolute;
  left: 5%;
  top: 11%;
  width: 74%;
  border: 1px solid var(--ink-600);
  border-radius: 7px;
  overflow: hidden;
  background: var(--ink-800);
  transform-origin: left center;
  transition: transform var(--t-press) var(--ease);
}
.mock-chrome {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 6px 8px;
  background: var(--band-b);
  border-bottom: 1px solid var(--ink-700);
}
/* Neutral dots. The usual red, amber, green would be the only red on the
   site and would fight the product's own use of red for deductions. */
.mock-chrome i {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--ink-600);
  flex: none;
}
.mock-url {
  margin-left: 6px;
  flex: 1;
  height: 12px;
  border-radius: 3px;
  background: var(--ink-900);
  border: 1px solid var(--ink-700);
}
.mock-browser img {
  width: 100%;
  height: auto;
  display: block;
  object-fit: cover;
  object-position: top left;
  aspect-ratio: 16 / 9;
}

.mock-phone {
  position: absolute;
  right: 9%;
  bottom: -6%;
  width: 19%;
  aspect-ratio: 1128 / 2325;
  border-radius: 12px;
  overflow: hidden;
  background: var(--ink-800);
  /* Ring, not shadow: a solid offset in the stage colour */
  box-shadow: 0 0 0 5px var(--band-a), 0 0 0 6px var(--ink-600);
  transition: transform var(--t-press) var(--ease);
}
.band-b .mock-phone { box-shadow: 0 0 0 5px var(--band-b), 0 0 0 6px var(--ink-600); }
.mock-phone img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }

/* The card tilts a fraction toward the reader on hover. Small enough to
   register as responsiveness, not as an effect. */
.work-item:hover .mock-browser { transform: translateX(-1.5%) rotateY(1.5deg); }
.work-item:hover .mock-phone   { transform: translateY(-4px); }

@media (max-width: 560px) {
  .mock-browser { width: 82%; left: 4%; }
  .mock-phone { width: 24%; right: 4%; }
}
@media (prefers-reduced-motion: reduce) {
  .work-item:hover .mock-browser,
  .work-item:hover .mock-phone { transform: none; }
}

.work-caption {
  margin-top: var(--s-3);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2);
}
.work-title {
  font-family: var(--font-mega);
  font-variation-settings: "wdth" 96;
  font-size: clamp(1.375rem, 1.1rem + 1vw, 1.875rem);
  letter-spacing: -0.025em;
  font-weight: 500;
  line-height: 1.2;
}
.work-title .arrow {
  display: inline-block;
  margin-left: 0.4em;
  color: var(--paper-faint);
  transition: transform var(--t-press) var(--ease), color var(--t-press) var(--ease);
}
.work-item:hover .arrow { transform: translateX(6px); color: var(--accent); }
.work-desc { margin-top: 0.4rem; color: var(--paper-dim); max-width: 52ch; }
.tags { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.tag {
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-faint);
  border: 1px solid var(--ink-600);
  border-radius: 999px;
  padding: 0.28rem 0.7rem;
  white-space: nowrap;
}

/* ---------- Case study hero ---------- */
.cs-hero { padding-block: clamp(4rem, 11vh, 8rem) var(--s-5); }
.cs-hero .display { max-width: 16ch; margin-top: var(--s-2); }
.cs-hero .lede { margin-top: var(--s-3); max-width: 62ch; }

/* Meta panel: role / results side by side */
.cs-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: clamp(2rem, 5vw, 4rem);
  padding-block: var(--s-4);
  border-top: 1px solid var(--ink-700);
  border-bottom: 1px solid var(--ink-700);
  margin-top: var(--s-5);
}
.cs-meta-col h3 { margin-bottom: var(--s-2); }
.cs-meta-list li {
  padding-left: 1.1rem;
  position: relative;
  color: var(--paper-dim);
  margin-bottom: 0.6rem;
  line-height: 1.5;
}
.cs-meta-list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.72em;
  width: 5px; height: 1px;
  background: var(--accent);
}
.cs-meta-list.facts li::before { background: var(--ink-500); }
.cs-meta-list strong { color: var(--paper); font-weight: 500; }

/* Stacked label/value pairs — avoids needing a dash separator */
.cs-meta-list .k {
  display: block;
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--paper-faint);
  margin-bottom: 0.15rem;
}
.cs-meta-list.facts li { margin-bottom: 1rem; color: var(--paper); }

/* Result numbers */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr));
  gap: var(--s-4);
  padding-block: var(--s-5);
}
.stat-num {
  font-family: var(--font-mega);
  font-variation-settings: "wdth" 92;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  font-size: clamp(2rem, 1.3rem + 2.6vw, 3.5rem);
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--paper);
}
.stat-label {
  margin-top: 0.7rem;
  color: var(--paper-dim);
  font-size: 0.875rem;
  line-height: 1.45;
  max-width: 28ch;
}

/* Quote sitting inside a column rather than across the page */
.quote-panel {
  border-top: 1px solid var(--ink-700);
  border-bottom: 1px solid var(--ink-700);
  padding-block: clamp(1.75rem, 3.5vw, 2.75rem);
  margin-top: var(--s-3);
}
.quote-panel blockquote {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.375rem, 1rem + 1.5vw, 2.125rem);
  line-height: 1.22;
  letter-spacing: -0.015em;
  color: var(--paper);
}
.quote-panel cite {
  display: block;
  margin-top: var(--s-2);
  font-family: var(--font-sans);
  font-style: normal;
  font-size: 0.8125rem;
  color: var(--paper-faint);
  letter-spacing: 0.02em;
}

/* ---------- Carousel ---------- */
.carousel { position: relative; }
.carousel-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  gap: 0;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.carousel-track::-webkit-scrollbar { display: none; }
.carousel-slide {
  flex: 0 0 100%;
  scroll-snap-align: start;
  margin: 0;
  min-width: 0;
}
.carousel-slide figcaption { margin-top: var(--s-2); }

.carousel-ui {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  margin-top: var(--s-3);
}

/* Phone variant: two devices visible, scroll through the rest */
.carousel--phone .carousel-track { gap: 1rem; }
.carousel--phone .carousel-slide { flex: 0 0 calc(50% - 0.5rem); }
.carousel--phone figcaption { font-size: 0.8125rem; }
@media (max-width: 560px) {
  .carousel--phone .carousel-slide { flex: 0 0 100%; }
}
.car-btn {
  appearance: none;
  background: transparent;
  border: 1px solid var(--ink-600);
  color: var(--paper-dim);
  width: 34px; height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  cursor: pointer;
  font-size: 0.9rem;
  line-height: 1;
  transition: border-color var(--t-press) var(--ease), color var(--t-press) var(--ease), background var(--t-press) var(--ease);
}
.car-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.car-btn:disabled { opacity: 0.3; cursor: default; }
.car-dots { display: flex; gap: 0.4rem; margin-inline: 0.3rem; }
.car-dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--ink-500);
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: background var(--t-press) var(--ease), transform var(--t-press) var(--ease);
}
.car-dot[aria-current="true"] { background: var(--accent); transform: scale(1.5); }
.car-count {
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  color: var(--paper-faint);
  font-variant-numeric: tabular-nums;
  min-width: 4.5ch;
  text-align: center;
}

/* Ceiling on image height. Tall assets (phone screens, the config calendar)
   were dominating the viewport; nothing should exceed a comfortable glance. */
.shot img { max-height: 68vh; object-fit: contain; object-position: center; margin-inline: auto; }
.carousel--phone .phone img { max-height: 60vh; }
figure.full .shot img { max-height: 62vh; }

/* ---------- Split layout: text beside image ----------
   The workhorse of the case study. Text column is sticky so a tall
   stack of screens scrolls against a fixed argument. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(2rem, 4vw, 4rem);
  align-items: center;
  padding-block: clamp(3rem, 6.5vw, 5.5rem);
}
.split-text > * + * { margin-top: var(--s-3); }
.split-text .eyebrow + .h2 { margin-top: var(--s-2); }
.split-text .h2 { max-width: 18ch; }
.split-text p { color: var(--paper-dim); }
.split-text p strong { color: var(--paper); font-weight: 500; }
.split-media > * + * { margin-top: clamp(1rem, 2.5vw, 2rem); }
.split-media figcaption { margin-top: var(--s-2); }

/* Alternate sides for rhythm */
@media (min-width: 901px) {
  .split.flip .split-text  { order: 2; }
  .split.flip .split-media { order: 1; }
}
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; gap: var(--s-4); }
  .split-text .h2 { max-width: 22ch; }
}

/* ---------- Case study body ---------- */
.chapter { padding-block: clamp(3.5rem, 8vw, 7rem); }
.chapter > * + * { margin-top: var(--s-3); }
.chapter .eyebrow + .h2 { margin-top: var(--s-2); }
.chapter .h2 { max-width: 20ch; }

/* Figure — the workhorse. Images do the talking. */
figure { margin-block: clamp(2.5rem, 6vw, 5rem); }
figure.full { margin-inline: 0; }
/* No frame, no fill, no shadow. The exports carry their own
   corners and shadows; anything we add fights them. */
.shot { position: relative; }
figcaption {
  margin-top: var(--s-2);
  font-size: 0.8125rem;
  color: var(--paper-faint);
  line-height: 1.5;
  text-align: left;
}

/* Phone screen grid — mobile UI reads better as a row of devices than one composite */
.phone-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr));
  gap: clamp(1rem, 2.5vw, 2rem);
  align-items: start;
}
.phone { transition: transform var(--t-press) var(--ease); }
.phone:hover { transform: translateY(-5px); }
.phone img { width: 100%; height: auto; display: block; }
.phone-cap {
  margin-top: 0.7rem;
  font-size: 0.8125rem;
  color: var(--paper-faint);
  line-height: 1.4;
}

/* Two-up image grid */
.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: clamp(1rem, 2.5vw, 2rem);
}

/* Pull quote — the one place the serif gets to sing */
.pullquote {
  padding-block: clamp(3rem, 7vw, 5.5rem);
  border-top: 1px solid var(--ink-700);
  border-bottom: 1px solid var(--ink-700);
  margin-block: clamp(3rem, 7vw, 5.5rem);
}
.pullquote blockquote {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 1rem + 2.4vw, 2.75rem);
  line-height: 1.22;
  letter-spacing: -0.015em;
  max-width: 24ch;
}
.pullquote cite {
  display: block;
  margin-top: var(--s-3);
  font-family: var(--font-sans);
  font-style: normal;
  font-size: 0.875rem;
  color: var(--paper-faint);
  letter-spacing: 0.02em;
}

/* Placeholder slots — visible content brief. Delete when real assets land. */
.slot {
  display: grid;
  place-content: center;
  text-align: center;
  gap: 0.5rem;
  padding: var(--s-4) var(--s-3);
  background:
    repeating-linear-gradient(
      -45deg,
      transparent 0 14px,
      rgba(255,255,255,0.014) 14px 28px
    ),
    var(--ink-800);
  color: var(--paper-faint);
}
.slot-label {
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}
.slot-desc { font-size: 0.875rem; max-width: 40ch; line-height: 1.5; }
.slot-dim { font-size: 0.75rem; color: var(--ink-500); letter-spacing: 0.05em; }
.ar-wide  { aspect-ratio: 16 / 9; }
.ar-tall  { aspect-ratio: 4 / 5; }
.ar-square{ aspect-ratio: 1 / 1; }
.ar-ultra { aspect-ratio: 21 / 9; }

/* ---------- Next / footer ---------- */
.next-up { padding-block: clamp(3.5rem, 8vw, 6rem); }
.next-up a { display: block; }
.next-up .work-title { margin-top: var(--s-2); }
.next-up a:hover .arrow { transform: translateX(6px); color: var(--accent); }

.site-footer {
  background: var(--footer-bg);
  color: var(--footer-fg);
  padding-block: var(--s-5) var(--s-4);
}
.site-footer .eyebrow,
.site-footer .dim,
.site-footer .colophon { color: var(--footer-dim); }
.site-footer .footer-links a { color: var(--footer-dim); }
.site-footer .footer-links a:hover { color: var(--footer-fg); }
.site-footer .contact-link { color: var(--footer-fg); border-bottom-color: var(--footer-line); }
.site-footer .contact-link:hover { color: #7fd0c3; border-bottom-color: #7fd0c3; }
.site-footer .colophon a { color: var(--footer-dim); }
.site-footer .colophon a:hover { color: var(--footer-fg); }
.footer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  gap: var(--s-4);
  align-items: start;
}
.contact-link {
  font-size: clamp(1.25rem, 1rem + 1.4vw, 2rem);
  letter-spacing: -0.025em;
  font-weight: 500;
  border-bottom: 1px solid var(--ink-600);
  padding-bottom: 0.1em;
  transition: border-color var(--t-press) var(--ease), color var(--t-press) var(--ease);
  display: inline-block;
}
.contact-link:hover { border-color: var(--accent); color: var(--accent); }
.footer-links { display: grid; gap: 0.5rem; font-size: 0.9375rem; }
.footer-links a { color: var(--paper-dim); transition: color var(--t-press) var(--ease); }
.footer-links a:hover { color: var(--paper); }
.colophon {
  border-top: 1px solid var(--footer-line);
  padding-top: var(--s-3);
  margin-top: var(--s-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-3);
  justify-content: space-between;
  font-size: 0.8125rem;
  color: var(--paper-faint);
}

/* ---------- User-need blocks (delete before launch) ----------
   Marks copy I need from Sara. Deliberately loud so none ship by accident. */
.gap {
  border-left: 2px solid var(--accent);
  background: var(--accent-soft);
  padding: var(--s-2) var(--s-3);
  margin-block: var(--s-3);
  border-radius: 0 3px 3px 0;
}
.gap-label {
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  display: block;
  margin-bottom: 0.4rem;
  font-weight: 600;
}
.gap p { font-size: 0.9375rem; line-height: 1.55; color: var(--paper); }
.gap ul { margin-top: 0.5rem; }
.gap li {
  font-size: 0.9375rem;
  line-height: 1.55;
  padding-left: 1rem;
  position: relative;
  margin-bottom: 0.35rem;
}
.gap li::before {
  content: "→";
  position: absolute;
  left: 0;
  color: var(--accent);
}

/* Three-user breakdown */
.user-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
  margin-top: var(--s-4);
}
.user-card {
  border-top: 1px solid var(--ink-600);
  padding-top: var(--s-2);
}
.user-card h4 {
  font-size: 1rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  margin-bottom: 0.5rem;
}
.user-card p { color: var(--paper-dim); font-size: 0.9375rem; line-height: 1.55; }

/* Bilingual icon legend */
.legend { margin-top: var(--s-3); display: grid; gap: var(--s-2); }
.legend-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.2rem;
  padding-block: var(--s-2);
  border-top: 1px solid var(--ink-700);
}
.legend-name { font-weight: 500; }
.legend-ar {
  font-size: 1.0625rem;
  color: var(--paper-dim);
  direction: rtl;
  unicode-bidi: isolate;
}
.legend-desc { color: var(--paper-dim); font-size: 0.9375rem; line-height: 1.5; }

/* Photograph, as opposed to a screenshot. Wants edge to edge and
   a hard ceiling, since it is atmosphere rather than evidence. */
.photo-band { padding-inline: 0; }
.photo-band img { width: 100%; max-height: 56vh; object-fit: cover; object-position: center 42%; }
.photo-band figcaption {
  padding-inline: max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter)));
  margin-top: var(--s-2);
}

/* ---------- Diagrams ----------
   Inline SVG so it inherits the theme and stays crisp at any size.
   Line weight and type scale deliberately match the page, not the SVG's own world. */
.diagram-frame {
  border: 1px solid var(--ink-700);
  border-radius: 4px;
  padding: clamp(1.25rem, 3vw, 2.25rem);
}
.diagram { display: block; width: 100%; height: auto; overflow: visible; }

.dg-box       { fill: var(--ink-700); stroke: var(--ink-600); stroke-width: 1; }
.dg-box-key   { fill: var(--ink-600); stroke: var(--ink-500); stroke-width: 1; }
.dg-box-acc   { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 1; }
.dg-box-ghost { fill: none; stroke: var(--ink-600); stroke-width: 1; stroke-dasharray: 3 4; }

.dg-line      { stroke: var(--ink-500); stroke-width: 1; fill: none; }
.dg-line-acc  { stroke: var(--accent); stroke-width: 1; fill: none; }
.dg-dot       { fill: var(--accent); }

.dg-bar       { fill: var(--ink-500); }
.dg-bar-dim   { fill: var(--ink-600); }
.dg-bar-num   { fill: var(--paper-dim); }
.dg-icon      { fill: var(--accent); }

.dg-label {
  fill: var(--paper);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
}
.dg-sub {
  fill: var(--paper-faint);
  font-family: var(--font-sans);
  font-size: 11px;
  letter-spacing: 0.01em;
}
.dg-cap {
  fill: var(--paper-faint);
  font-family: var(--font-sans);
  font-size: 10px;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
.dg-num {
  fill: var(--paper);
  font-family: var(--font-display);
  font-size: 26px;
}
.dg-num-acc { fill: var(--accent); }

/* ---------- Press feedback ----------
   Anything clickable acknowledges the press. Without it the interface
   feels dead on touch, where there is no hover to fall back on. */
.theme-toggle:active,
.car-btn:active:not(:disabled) { transform: scale(0.96); }
.work-item:active .work-thumb { transform: translateY(-2px) scale(0.995); }
.nav a:active, .footer-links a:active, .contact-link:active { opacity: 0.7; }
.car-dot:active { transform: scale(1.3); }

/* ---------- Hit targets ----------
   Fitts's law: 32px minimum. Several controls are visually smaller than
   that on purpose, so the target is expanded with a pseudo-element
   instead of by inflating the design. */
.theme-toggle { min-height: 34px; }
.car-btn { width: 36px; height: 36px; }

.nav a, .footer-links a { position: relative; }
.nav a::after, .footer-links a::after {
  content: "";
  position: absolute;
  inset: -10px -8px;
}
/* Dots are secondary to the arrows, so the expansion stops short of
   overlapping its neighbour. Overlapping targets are worse than small ones. */
.car-dots { gap: 0.75rem; }
.car-dot { position: relative; }
.car-dot::after {
  content: "";
  position: absolute;
  inset: -11px -5px;
}

/* ---------- Reveal on scroll ---------- */
.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--t-enter) var(--ease), transform var(--t-enter) var(--ease);
}
.reveal.is-in { opacity: 1; transform: none; }

/* Cross document navigation, handled by the browser rather than a library.
   Chrome animates the page change; everywhere else it is simply ignored. */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation-timing-function: var(--ease-in); animation-duration: 140ms; }
::view-transition-new(root) { animation-timing-function: var(--ease); animation-duration: var(--t-enter); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  .work-item:active .work-thumb,
  .theme-toggle:active,
  .car-btn:active:not(:disabled) { transform: none; }
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  @view-transition { navigation: none; }
}

/* ---------- Focus ---------- */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}
.skip {
  position: absolute;
  left: -9999px;
}
.skip:focus {
  left: var(--gutter);
  top: 0.5rem;
  z-index: 200;
  background: var(--paper);
  color: var(--ink-900);
  padding: 0.6rem 1rem;
  border-radius: 3px;
}

/* ============================================================
   FUTURIST LAYER
   Dark ground, engineered display type, a technical mono register,
   and three motion devices: line reveals, a marquee, and
   scroll-driven scale. Everything degrades to static.
   ============================================================ */

/* ---------- Technical register ----------
   Mono labels in parentheses. They read as machine annotation rather
   than as headings, which is most of where the "futuristic" feeling
   actually comes from. Not from any single effect. */
.mono {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-faint);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}
.mono-accent { color: var(--accent); }
.mono-bright { color: var(--paper-dim); }

/* ---------- Display type ----------
   Archivo is variable on width, so the headline is stretched to the
   measure instead of tracked out. Tight leading, negative tracking. */
.mega {
  font-family: var(--font-mega);
  font-weight: 500;
  font-variation-settings: "wdth" 96;
  font-size: clamp(2.5rem, 1rem + 7.2vw, 8rem);
  line-height: 0.94;
  letter-spacing: -0.04em;
  text-wrap: balance;
}
.mega-sm {
  font-family: var(--font-mega);
  font-weight: 500;
  font-variation-settings: "wdth" 96;
  font-size: clamp(1.7rem, 0.9rem + 3.4vw, 3.6rem);
  line-height: 1;
  letter-spacing: -0.032em;
}
.mega .accent, .mega-sm .accent { color: var(--accent); }

/* ---------- Line by line reveal ----------
   Each line is masked by its own overflow box and slides up from
   under it. 40ms apart, 280ms each, so the whole headline is
   resolved well inside the time it takes to read the first line. */
.lines { display: block; }
.line {
  display: block;
  overflow: hidden;
  padding-bottom: 0.06em;   /* room for descenders inside the mask */
}
.line > span {
  display: block;
  transform: translateY(105%);
  transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1);
}
.is-in .line > span,
.line.is-in > span { transform: translateY(0); }
.line:nth-child(1) > span { transition-delay: 0ms; }
.line:nth-child(2) > span { transition-delay: 40ms; }
.line:nth-child(3) > span { transition-delay: 80ms; }
.line:nth-child(4) > span { transition-delay: 120ms; }
.line:nth-child(5) > span { transition-delay: 160ms; }

/* ---------- Marquee ----------
   Two identical tracks, the second exactly one track behind, so the
   loop is seamless at any width. Duration scales with content, not
   with the viewport, so the speed is constant on every screen. */
.marquee {
  --marquee-duration: 38s;
  position: relative;
  display: flex;
  overflow: hidden;
  border-block: 1px solid var(--ink-700);
  padding-block: clamp(0.75rem, 1.6vw, 1.1rem);
  user-select: none;
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.marquee-track {
  display: flex;
  flex: none;
  gap: 0;
  min-width: 100%;
  animation: marquee var(--marquee-duration) linear infinite;
  will-change: transform;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
.marquee-item {
  font-family: var(--font-mega);
  font-weight: 500;
  font-variation-settings: "wdth" 92;
  font-size: clamp(1.1rem, 0.6rem + 1.6vw, 2rem);
  letter-spacing: -0.02em;
  color: var(--paper);
  white-space: nowrap;
  padding-inline: clamp(1rem, 2.5vw, 2.25rem);
  display: inline-flex;
  align-items: center;
  gap: clamp(1rem, 2.5vw, 2.25rem);
}
.marquee-item::after {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}
@keyframes marquee { to { transform: translateX(-100%); } }

/* ---------- Scroll driven motion ----------
   Native scroll timelines where supported: no scroll listener, no
   main thread work, correct on fast flicks. Silently absent elsewhere. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .scroll-scale {
      animation: scroll-scale linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 42%;
    }
    @keyframes scroll-scale {
      from { transform: scale(1.14); }
      to   { transform: scale(1); }
    }
    .scroll-rise {
      animation: scroll-rise linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 90%;
    }
    @keyframes scroll-rise {
      from { opacity: 0; transform: translateY(28px); }
      to   { opacity: 1; transform: none; }
    }
    /* Parallax drift: the media lags the page slightly on the way past */
    .scroll-drift {
      animation: scroll-drift linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    @keyframes scroll-drift {
      from { transform: translateY(-4%); }
      to   { transform: translateY(4%); }
    }
  }
}
.scroll-clip { overflow: hidden; }

/* ---------- Status grid ---------- */
.status {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
  gap: clamp(1.25rem, 3vw, 2.5rem);
  border-top: 1px solid var(--ink-700);
  padding-top: var(--s-3);
  margin-top: clamp(2.5rem, 6vw, 4.5rem);
}
.status dt { margin-bottom: 0.45rem; }
.status dd {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.45;
  color: var(--paper);
}

/* ---------- Capabilities ---------- */
.cap-list { border-top: 1px solid var(--ink-700); }
.cap {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  align-items: start;
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
  border-bottom: 1px solid var(--ink-700);
  transition: background var(--t-press) var(--ease);
}
.cap:hover { background: var(--ink-800); }
.cap-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.4rem; }
@media (min-width: 780px) {
  .cap { grid-template-columns: 4.5rem minmax(0, 0.9fr) minmax(0, 1.1fr); }
  .cap-body { grid-column: 2 / 4; grid-template-columns: subgrid; align-items: baseline; }
}
.cap h3 {
  font-family: var(--font-mega);
  font-variation-settings: "wdth" 98;
  font-weight: 500;
  font-size: clamp(1.25rem, 1rem + 1vw, 1.875rem);
  letter-spacing: -0.025em;
  line-height: 1.1;
}
.cap p { color: var(--paper-dim); font-size: 0.9375rem; line-height: 1.5; }

/* ---------- Live clock ---------- */
.clock {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
}

/* ---------- Media slots for assets not in yet ---------- */
.slot-dark {
  display: grid;
  place-content: center;
  gap: 0.5rem;
  text-align: center;
  padding: var(--s-4) var(--s-3);
  border: 1px dashed var(--ink-600);
  background:
    repeating-linear-gradient(-45deg, transparent 0 16px, rgba(255,255,255,0.012) 16px 32px),
    var(--ink-800);
  color: var(--paper-faint);
}

@media (prefers-reduced-motion: reduce) {
  .line > span { transform: none; transition: none; }
  .marquee-track { animation: none; }
  .marquee { mask-image: none; -webkit-mask-image: none; }
}

/* Screen reader only. The marquee itself is aria-hidden because a looping
   duplicated strip is nonsense to read aloud; this carries the content once. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- State carousel ----------
   One state per slide: the screen on the left, the reasoning for that
   state on the right. Six states is too many to stack down a page and
   too few to justify a grid where none of them can be read properly. */
.carousel--state .carousel-track { gap: 0; }
.carousel--state .carousel-slide { flex: 0 0 100%; padding-inline: 2px; }
.state-slide {
  display: grid;
  grid-template-columns: clamp(240px, 28vw, 360px) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: center;
}
@media (max-width: 760px) {
  .state-slide { grid-template-columns: 1fr; gap: var(--s-4); }
  .state-screen { max-width: 300px; margin-inline: auto; }
}

/* Device frame. The exports already contain the iOS status bar, so the
   bezel is plain: no notch, nothing to duplicate what is in the image.
   Inner radius is outer minus padding, so the corners stay concentric. */
.state-screen {
  position: relative;
  padding: 10px;
  border-radius: 44px;
  background: var(--ink-700);
  box-shadow: inset 0 0 0 1px var(--ink-600);
}
.state-screen::after {
  content: "";
  position: absolute;
  inset: 10px;
  border-radius: 34px;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07);
  pointer-events: none;
}
[data-theme="light"] .state-screen::after { box-shadow: inset 0 0 0 1px rgba(23, 22, 20, 0.10); }
.state-figure { max-width: 330px; }
.state-figure figcaption { max-width: 100%; }
.state-screen img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 34px;
  background: #fff;
}

.state-meta > * + * { margin-top: var(--s-2); }
.state-meta h3 {
  font-family: var(--font-mega);
  font-variation-settings: "wdth" 98;
  font-weight: 500;
  font-size: clamp(1.25rem, 1rem + 1.1vw, 2rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
}
.state-meta .state-why { color: var(--paper-dim); max-width: 56ch; }
.state-list { margin-top: var(--s-3); display: grid; gap: 0.7rem; }
.state-list li {
  position: relative;
  padding-left: 1.2rem;
  color: var(--paper-dim);
  font-size: 0.9375rem;
  line-height: 1.5;
  max-width: 60ch;
}
.state-list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.68em;
  width: 6px; height: 1px;
  background: var(--accent);
}

/* Problem list: the four things wrong with the screen we started from */
.problems { display: grid; gap: 0; border-top: 1px solid var(--ink-700); margin-top: var(--s-4); }
.problem {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  padding-block: clamp(1.25rem, 2.5vw, 1.75rem);
  border-bottom: 1px solid var(--ink-700);
}
.problem .eyebrow, .cap .eyebrow, .cap .mono, .state-meta .eyebrow { white-space: nowrap; }
.problem h4 { font-weight: 500; font-size: 1.0625rem; letter-spacing: -0.01em; margin-bottom: 0.3rem; }
.problem p { color: var(--paper-dim); font-size: 0.9375rem; line-height: 1.55; max-width: 62ch; }


/* ============ PLACEHOLDER MARKERS ============ */
/* Marks copy that is waiting on real content. Delete this rule once the
   page has no .tbd left in it, and the class becomes harmless if missed. */
.tbd {
  color: var(--accent);
  border-bottom: 1px dashed currentColor;
}

/* ============ STANDALONE BROWSER FRAME ============
   The mock-browser above is positioned inside a work thumbnail stage. This
   is the same chrome as a normal flow element, for admin and web screens
   shown at full size in a case study. */
.frame {
  border: 1px solid var(--ink-600);
  border-radius: 8px;
  overflow: hidden;
  background: var(--ink-800);
  box-shadow: 0 18px 40px rgba(23, 22, 20, 0.10);
}
.frame-chrome {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 8px 10px;
  background: var(--band-b);
  border-bottom: 1px solid var(--ink-700);
}
.frame-chrome i {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--ink-600);
  flex: none;
}
.frame-url {
  margin-left: 8px;
  flex: 1;
  height: 14px;
  border-radius: 3px;
  background: var(--ink-900);
  border: 1px solid var(--ink-700);
}
.frame img { width: 100%; height: auto; display: block; }
/* Caption belongs to the frame, so it matches its width rather than the column. */
.frame-figure { margin: 0; }
.frame-figure figcaption {
  margin-top: var(--s-2);
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--paper-faint);
}

/* ============ OPENER PHONE ROW ============ */
.phone-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  align-items: start;
  max-width: 940px;
  margin-inline: auto;
}
@media (max-width: 720px) {
  .phone-row { grid-template-columns: 1fr; max-width: 300px; }
}

/* ============ ADMIN STAGE ============
   A back office screenshot is only worth showing if the fields are
   legible, and a half width column cannot do that. So the frame takes
   most of the row, the rationale sits beside it in a narrow column, and
   the frame stays put while that column advances through the carousel. */
.admin-stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 460px);
  gap: clamp(1.5rem, 3.5vw, 3rem);
  align-items: center;
}
@media (max-width: 1000px) {
  .admin-stage { grid-template-columns: 1fr; gap: var(--s-4); }
}

/* The device. Nav is a fixed column, the form scrolls inside it.
   Scroll chaining is left at the browser default on purpose: reaching the
   bottom of the form hands the gesture back to the page rather than
   trapping it. */
.admin-device .frame-chrome { position: relative; z-index: 2; }
.admin-body {
  display: flex;
  height: clamp(380px, 52vh, 545px);
  background: #fff;
}
.admin-nav {
  flex: 0 0 18.4%;   /* 624 : 2760, the source ratio, so both halves scale together */
  overflow: hidden;
  background: #0d0d2e;
}
.admin-nav img { width: 100%; height: auto; display: block; }
.admin-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  background: #fff;
  scrollbar-width: thin;
  scrollbar-color: var(--ink-600) transparent;
}
.admin-scroll::-webkit-scrollbar { width: 9px; }
.admin-scroll::-webkit-scrollbar-thumb {
  background: var(--ink-600);
  border-radius: 5px;
  border: 2px solid #fff;
}
.admin-scroll img { width: 100%; height: auto; display: block; }
.admin-hint {
  margin-top: var(--s-2);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-faint);
}



/* Text only carousel. No media column, so the slide is just the prose. */
.carousel--text .carousel-track { gap: 0; }
.carousel--text .carousel-slide { flex: 0 0 100%; padding-inline: 2px; }
.carousel--text .carousel-ui { justify-content: flex-start; }
.carousel--text .state-meta { max-width: none; }
.carousel--text .state-why { max-width: none; }

/* ============ WORK ROWS ============
   A row per project: what it is on the left, what it looks like on the
   right. The strip runs past the right edge of the page rather than
   stopping at the margin, so the work reads as a continuing body rather
   than three finished cards. */
.work-full {
  /* Left edge lines up with every other section. Right edge does not
     exist, which is the whole point. */
  padding-left: max(var(--gutter), calc((100% - var(--max)) / 2 + var(--gutter)));
  padding-right: 0;
  border-top: 1px solid var(--ink-700);
}
.work-row {
  display: grid;
  grid-template-columns: minmax(220px, 27%) minmax(0, 1fr);
  gap: clamp(1rem, 3vw, 2.5rem);
  align-items: center;
  border-bottom: 1px solid var(--ink-700);
  padding-block: clamp(1.4rem, 2.8vw, 2rem);
  transition: background var(--t-press) var(--ease);
}
.work-row:hover { background: var(--band-a); }
.work-row-text > * + * { margin-top: 0.55rem; }
.work-row .work-title { font-size: clamp(1.35rem, 0.9rem + 1.5vw, 2.1rem); }
.work-line {
  color: var(--paper-dim);
  font-size: 0.9375rem;
  line-height: 1.5;
  max-width: 32ch;
}

/* The strip. Two identical tracks, each shifted by its own width, so the
   loop has no seam and no gap arithmetic to get wrong. */
.work-strip {
  --strip-gap: clamp(8px, 1vw, 14px);
  display: flex;
  overflow: hidden;
  /* No gap here. Each track already carries a trailing gap, so a gap on
     the flex parent would double the space at the seam between the two
     tracks and nowhere else. */
  gap: 0;
}
.work-strip-track {
  display: flex;
  flex: none;
  gap: var(--strip-gap);
  padding-right: var(--strip-gap);
  animation: work-strip var(--strip-duration, 48s) linear infinite;
  will-change: transform;
}
.work-row:hover .work-strip-track { animation-play-state: paused; }
@keyframes work-strip { to { transform: translateX(-100%); } }

.work-strip img {
  height: clamp(96px, 11vw, 148px);
  width: auto;
  flex: none;
  display: block;
  border-radius: 6px;
  background: var(--ink-800);
  outline: 1px solid var(--ink-700);
  outline-offset: -1px;
}

@media (max-width: 760px) {
  .work-row { grid-template-columns: 1fr; gap: var(--s-3); }
  .work-strip img { height: 92px; }
}


/* ============ HERO FILL ============
   The headline column matches the portrait's height rather than sitting
   under it, so the two sides of the hero end on the same line. Lines
   distribute into whatever height the image sets. */
/* The portrait now sets the height of the hero and the headline centres
   against it. That makes the whole section tall enough that the work list
   starts below the fold, which is the point: the first screen should be
   who this is, not a list of links. */
.hero-fill {
  font-size: clamp(2rem, 0.9rem + 4.2vw, 4.5rem);
  line-height: 1.04;
  margin: 0;
}
/* Instrument Serif italic rather than a slanted Archivo. The display face
   is loaded without an italic axis, so italic on it would be synthesised
   and would fall apart at this size. The serif also gives the greeting a
   different voice from the claim, which is the point of setting it apart. */
.hero-fill .serif-em {
  font-size: 1.06em;   /* the serif runs optically smaller at the same size */
  letter-spacing: -0.015em;
  color: var(--paper-dim);   /* same token as the wordmark subtitle */
}
@media (min-width: 780px) {
  .hero-grid {
    grid-template-columns: minmax(0, 1fr) clamp(300px, 36vw, 460px);
    align-items: center;
  }
  .hero-media { position: static; height: auto; min-height: 0; }
  .hero-media img { position: static; aspect-ratio: 4 / 5; height: auto; }
}
@media (max-width: 779px) {
  .hero-media img { aspect-ratio: 4 / 5; max-width: 320px; }
}

/* ============ CAPABILITY WORDS ============
   Set as a block of language rather than a list. No numbers, no rules,
   no descriptions: fifteen claims that read as one paragraph. Clicking
   one marks it, which is the only interaction on the page that persists. */
.cap-cloud {
  /* Instrument Sans rather than Archivo: the display face bottoms out at
     400 and still reads heavy at this size. The text face at 400 is the
     thinner thing that was actually being asked for. */
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: clamp(1.2rem, 0.85rem + 1.55vw, 2.15rem);
  line-height: 1.42;
  letter-spacing: -0.01em;
  max-width: none;
  /* Justified so both edges are flush and the words form a block rather
     than a ragged column. The last line is justified too, which is the
     only way the block closes square at the bottom right. */
  text-align: justify;
  text-align-last: justify;
  -webkit-hyphens: none;
  hyphens: none;
}
.cap-word {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  letter-spacing: inherit;
  color: var(--paper);
  cursor: pointer;
  transition: color var(--t-press) var(--ease);
}
/* The separator lives on the element rather than between them, so the
   words wrap as one continuous paragraph instead of a grid of chips. */
.cap-word:not(:last-child)::after {
  content: " · ";
  color: var(--paper-faint);
}
/* A separator that lands at the end of a wrapped line is punctuation with
   nothing after it. Hidden rather than removed, so the glyph keeps its
   width and the line breaks it was measured against do not shift. */
.cap-word.line-end::after { visibility: hidden; }
.cap-word:hover,
.cap-word:focus-visible { color: var(--accent); }
.cap-word[aria-pressed="true"] { color: var(--accent); }
.cap-word:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }
.cap-word:not(:last-child):hover::after,
.cap-word:not(:last-child)[aria-pressed="true"]::after { color: var(--paper-faint); }

/* ============ CAPABILITIES LIST (unused) ============
   No descriptions. The list is a set of claims, and a paragraph after
   each one turns claims into hedging. Hover is the only colour. */
.cap-list {
  list-style: none;
  padding: 0;
  margin: 0;
  border-top: 1px solid var(--ink-700);
  columns: 1;
}
.cap-item {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  gap: clamp(0.75rem, 2vw, 2rem);
  align-items: baseline;
  padding-block: clamp(0.85rem, 1.7vw, 1.25rem);
  border-bottom: 1px solid var(--ink-700);
  color: var(--paper);
  transition: color var(--t-press) var(--ease);
}
.cap-item:hover { color: var(--accent); }
.cap-item:hover .mono { color: var(--accent); }
.cap-item h3 {
  font-family: var(--font-mega);
  font-variation-settings: "wdth" 98;
  font-weight: 500;
  font-size: clamp(1.15rem, 0.9rem + 1vw, 1.75rem);
  letter-spacing: -0.025em;
  line-height: 1.1;
  color: inherit;
  margin: 0;
}
.cap-item .mono { transition: color var(--t-press) var(--ease); }

@media (min-width: 900px) {
  .cap-list { columns: 2; column-gap: clamp(2rem, 5vw, 4.5rem); }
  .cap-item { break-inside: avoid; }
}

/* ============================================================
   MOBILE PASS
   Nothing on this site may scroll horizontally. Everything below
   either prevents that or fixes what small screens broke.
   ============================================================ */

/* overflow-x: clip rather than hidden. hidden on html or body silently
   kills position: sticky, and the header is sticky. */
html, body { overflow-x: clip; }

/* Grid and flex children default to min-width: auto, which lets a wide
   child push its track past the viewport. This is the usual culprit
   behind a page that scrolls sideways for no visible reason. */
.split, .state-slide, .admin-stage, .work-row, .carousel-slide, .grid-2,
.split > *, .state-slide > *, .admin-stage > *, .work-row > *, .grid-2 > * {
  min-width: 0;
}
.shot img, .frame img, .state-screen img, .phone img { max-width: 100%; }

/* The arrow should follow the last word of the title. text-wrap: balance
   evens the line lengths, which can leave the arrow stranded alone on the
   final line. */
.work-title { text-wrap: normal; }

/* Pause on hover only where hovering exists. On a touch screen every tap
   registers as a hover and the strip stops dead. */
@media (hover: hover) {
  .work-row:hover .work-strip-track { animation-play-state: paused; }
}

.work-strip-track { transform: translateZ(0); }

@media (max-width: 760px) {
  /* Projects: more air between rows, and inside each one. */
  .work-row {
    padding-block: clamp(2rem, 7vw, 2.75rem);
    gap: 1.35rem;
    align-items: start;
  }
  .work-row-text > * + * { margin-top: 0.7rem; }
  .work-row .work-title { font-size: 1.5rem; line-height: 1.15; }
  .work-line { font-size: 0.9375rem; max-width: none; }
  .work-row .tags { gap: 0.35rem; }

  /* The strip: a wider gap so frames cannot touch, and a taller image so
     the row still reads as work rather than as decoration. */
  .work-strip { --strip-gap: 12px; }
  .work-strip img { height: 104px; }

  /* Justifying a short measure opens rivers of white space between words.
     Ragged right is the correct call at this width. */
  .cap-cloud {
    text-align: left;
    text-align-last: left;
    font-size: clamp(1.1rem, 4.4vw, 1.5rem);
    line-height: 1.5;
  }
}

/* The admin panel keeps its scrollable frame on mobile. The side nav is
   dropped because at this width it would be 60px of unreadable labels,
   but the thing that matters, scrolling the real form inside a device,
   survives. */
@media (max-width: 1000px) {
  .admin-body { height: clamp(360px, 58vh, 520px); }
  .admin-nav { display: none; }
  .admin-scroll { overflow-y: auto; }
  .admin-hint { display: block; }
}

/* The actual cause of the wide dashboards and the sideways scroll in the
   case studies: a grid item defaults to min-width: auto, so a 4320px
   screenshot forces its column wider than the viewport, and max-width:100%
   on the image then resolves against that already oversized column.
   Zeroing the minimum lets the column shrink and the image follow. */
.split-media, .split-text, .state-meta, .state-screen,
.frame, .frame-figure, .grid2 > *, .grid3 > *, figure { min-width: 0; }

svg { max-width: 100%; height: auto; }

@media (max-width: 760px) {
  /* Long eyebrows would rather wrap than push the page sideways. */
  .problem .eyebrow, .cap .eyebrow, .cap .mono, .state-meta .eyebrow {
    white-space: normal;
  }
  /* Case study images sit inside a single column at this width. */
  .shot img, figure.full .shot img { max-height: none; }
  .state-figure { max-width: 100%; }
}
