/* ==========================================================================
   Textum Media — site.css
   Reset, base typography, layout primitives and every shared component.
   Loaded after tokens.css. Every value comes from a token (a literal 1px
   hairline is the only exception). No !important, no id selectors, no hex.

   CONTEXT TOKENS
   --------------
   Components read "context tokens" instead of hardcoding paper/ink colours,
   so the same component works on both surfaces. A container that flips the
   surface (.section--ink, .cta-band, .footer, .nav__overlay, .card--ink or
   the escape hatch .on-ink) redefines them; everything inside adapts.

     --surface     background of the current surface
     --surface-2   alternate/raised surface (table heads, frame chrome)
     --surface-card card background on the current surface
     --fg          primary text
     --fg-2        secondary text
     --rule        hairline
     --rule-2      stronger hairline (control edges, outlines)
     --mark        eyebrow bracket / small accent
     --link        inline link colour
     --link-hover  inline link hover colour
     --ring        focus ring colour

   TABLE OF CONTENTS
   -----------------
   01 Reset & document
   02 Surface contexts
   03 Base typography
   04 Utilities
   05 Skip link
   06 Layout: container, section, grid
   07 Buttons
   08 Eyebrow, decorative bracket, highlight
   09 Nav
   10 Footer
   11 Card, pill
   12 Stat
   13 Steps
   14 Editorial rows
   15 Checklist
   16 Accordion
   17 Form field
   18 Notice
   19 Table
   20 Badge
   21 Marquee
   22 Frame (browser chrome) + frame stack
   23 Portfolio carousel
   24 Hero & page head
   25 CTA band
   26 Legal prose
   27 Focus
   27b Scroll progress, back-to-top, copy chip, cursor badge, media link
   28 Motion (every transition + animation lives here, reduced-motion safe)
   29 Print
   ========================================================================== */


/* == 01 Reset & document ================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd, ol, ul, fieldset, legend {
  margin: 0;
  padding: 0;
}

ul[class],
ol[class] {
  list-style: none;
}

img,
svg,
video {
  display: block;
  max-width: 100%;
}

img {
  height: auto;
}

svg {
  flex: none;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: 0;
  cursor: pointer;
}

textarea {
  resize: vertical;
}

fieldset {
  border: 0;
  min-inline-size: 0;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: 0;
}

main {
  display: block;
  scroll-margin-top: var(--nav-h);
}

/* Any in-page anchor target clears the sticky header. */
:target {
  scroll-margin-top: calc(var(--nav-h) + var(--space-3));
}

[hidden] {
  display: none;
}


/* == 02 Surface contexts ================================================== */

:root {
  --surface: var(--paper);
  --surface-2: var(--paper-2);
  --surface-card: var(--card);
  --fg: var(--text);
  --fg-2: var(--text-2);
  --rule: var(--line);
  --rule-2: var(--line-2);
  --mark: var(--signal);
  --link: var(--signal);
  --link-hover: var(--signal-2);
  --ring: var(--focus);
}

/* The flag surface. Yellow is a block, so everything on it is ink: there is
   no light-on-yellow state anywhere in the system. */
.section--flag,
.on-flag {
  --surface: var(--flag);
  --surface-2: var(--flag);
  --surface-card: var(--flag);
  --fg: var(--on-flag);
  --fg-2: var(--on-flag-2);
  --rule: var(--on-flag-line);
  --rule-2: var(--on-flag-line);
  --mark: var(--on-flag);
  --accent: var(--on-flag);
  --link: var(--link-on-flag);
  --link-hover: var(--on-flag);
  --ring: var(--on-flag);
  background-color: var(--flag);
  color: var(--fg);
}

.section--ink,
.cta-band,
.footer,
.nav__overlay,
.card--ink,
.notice--error,
.on-ink {
  --surface: var(--ink);
  --surface-2: var(--ink-2);
  --surface-card: var(--ink-2);
  --fg: var(--text-inv);
  --fg-2: var(--text-inv-2);
  --rule: var(--line-inv);
  --rule-2: var(--line-inv-2);
  --mark: var(--text-inv);
  /* The paper-surface accents are all too dark to read on ink. Anything
     painting from --accent on an ink surface gets the light sibling; a
     .t-* class deeper in the tree still overrides this with its own. */
  --accent: var(--a-blue-lt);
  --accent-soft: rgba(143, 160, 255, .10);
  --accent-soft-2: rgba(143, 160, 255, .20);
  --link: var(--text-inv);
  --link-hover: var(--text-inv);
  --ring: var(--text-inv);
  color: var(--fg);
}


/* == 03 Base typography =================================================== */

body {
  background-color: var(--paper);
  color: var(--text);
  /* The portfolio carousel is a horizontal scroller; `clip` guarantees its
     track can never widen the page. `clip` (not `hidden`) does not create a
     scroll container, so the sticky nav keeps working. */
  overflow-x: clip;
  font-family: var(--font-text);
  font-size: var(--fs-md);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-wrap: break-word;
}

/* Archivo is a variable face and the WIDTH axis is what gives the headings
   their voice: set expanded, they read as signage rather than as another
   grotesque. font-stretch is the standard property for wdth; the
   font-variation-settings line is the fallback for engines that only take
   the axis directly. Body copy stays at normal width. */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-stretch: 112%;
  font-variation-settings: 'wdth' var(--wdth-display);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-heading);
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
  color: var(--fg);
}

h1 {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-display);
  line-height: var(--lh-display);
}

h2 {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
}

h3 {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
}

h4 {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight);
}

h5, h6 {
  font-family: var(--font-text);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  margin: 0;
  color: var(--fg);
}

p {
  text-wrap: pretty;
}

a {
  color: var(--link);
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
}

a:hover {
  color: var(--link-hover);
}

strong,
b {
  font-weight: var(--fw-bold);
}

small {
  font-size: var(--fs-sm);
}

code,
kbd,
samp,
pre {
  font-family: var(--font-mono);
  font-size: .9em;
}

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


/* == 04 Utilities ========================================================= */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Body copy that must not run wider than a comfortable measure. */
.measure {
  max-width: var(--measure);
}

.measure-narrow {
  max-width: var(--measure-narrow);
}

/* Lead paragraph under a headline. */
.lede {
  font-size: var(--fs-lg);
  line-height: 1.5;
  color: var(--fg-2);
  max-width: var(--measure);
}

/* Small monospace annotation line (location, reply promise, meta). */
.meta {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--fg-2);
  line-height: 1.5;
}

.text-2 {
  color: var(--fg-2);
}

.hairline-top {
  border-top: 1px solid var(--rule);
}

.hairline-bottom {
  border-bottom: 1px solid var(--rule);
}

/* Vertical rhythm helpers — margin-top on every child but the first. */
.stack > * + * {
  margin-top: var(--stack-space, var(--space-3));
}

.stack-sm {
  --stack-space: var(--space-2);
}

.stack-lg {
  --stack-space: var(--space-5);
}

/* Horizontal wrapping row of items (buttons, pills, links). */
.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cluster-space, var(--space-2));
}

/* Inline SVG arrow inside links and buttons. */
.arrow {
  width: 18px;
  height: 18px;
  stroke: currentColor;
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Standalone text link with an arrow. */
.link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--link);
  text-decoration: none;
  border-bottom: 1px solid var(--rule-2);
  padding-bottom: 2px;
}

.link:hover {
  border-bottom-color: currentColor;
}


/* == 05 Skip link ========================================================= */

.skip-link {
  position: fixed;
  top: var(--space-1);
  left: var(--space-1);
  z-index: var(--z-skip);
  transform: translateY(calc(-100% - var(--space-4)));
  display: inline-block;
  padding: var(--space-2) var(--space-3);
  background-color: var(--signal);
  color: var(--on-signal);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-decoration: none;
  border-radius: var(--r-sm);
}

.skip-link:focus {
  transform: translateY(0);
  color: var(--on-signal);
}


/* == 06 Layout: container, section, grid ================================== */

.container {
  width: 100%;
  max-width: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container--narrow {
  max-width: calc(var(--container-narrow) + var(--gutter) * 2);
}

.section {
  position: relative;
  padding-block: var(--space-section);
}

.section--tight {
  padding-block: var(--space-9);
}

/* A recessed band. One step darker than the ground, no hairline needed —
   the tone change is the division. */
.section--sunken {
  --surface: var(--paper-2);
  background-color: var(--paper-2);
}

.section--ink {
  background-color: var(--ink);
  overflow: hidden;
}

/* Header block that opens a section: eyebrow + heading + optional lede. */
.section__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-8);
}

.section__head--split {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--space-4);
}

.section__title {
  max-width: 18ch;
}

.grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  column-gap: var(--grid-gap-x);
  row-gap: var(--grid-gap-y);
}

@media (min-width: 901px) {
  .grid {
    grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  }

  .col-1 { grid-column-end: span 1; }
  .col-2 { grid-column-end: span 2; }
  .col-3 { grid-column-end: span 3; }
  .col-4 { grid-column-end: span 4; }
  .col-5 { grid-column-end: span 5; }
  .col-6 { grid-column-end: span 6; }
  .col-7 { grid-column-end: span 7; }
  .col-8 { grid-column-end: span 8; }
  .col-9 { grid-column-end: span 9; }
  .col-10 { grid-column-end: span 10; }
  .col-11 { grid-column-end: span 11; }
  .col-12 { grid-column-end: span 12; }

  .col-start-1 { grid-column-start: 1; }
  .col-start-2 { grid-column-start: 2; }
  .col-start-3 { grid-column-start: 3; }
  .col-start-4 { grid-column-start: 4; }
  .col-start-5 { grid-column-start: 5; }
  .col-start-6 { grid-column-start: 6; }
  .col-start-7 { grid-column-start: 7; }
  .col-start-8 { grid-column-start: 8; }
  .col-start-9 { grid-column-start: 9; }
  .col-start-10 { grid-column-start: 10; }
  .col-start-11 { grid-column-start: 11; }
  .col-start-12 { grid-column-start: 12; }
}


/* == 07 Buttons =========================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: 14px var(--space-3);
  min-height: 48px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-family: var(--font-text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: 1.2;
  letter-spacing: var(--tracking-tight);
  text-decoration: none;
  text-align: center;
  cursor: pointer;
}

.btn--sm {
  min-height: 40px;
  padding: 10px var(--space-2);
  font-size: var(--fs-xs);
}

.btn--lg {
  min-height: 56px;
  padding: 16px var(--space-4);
  font-size: var(--fs-md);
}

.btn--block {
  width: 100%;
}

.btn--primary {
  background-color: var(--signal);
  border-color: var(--signal);
  color: var(--on-signal);
}

.btn--primary:hover {
  background-color: var(--signal-2);
  border-color: var(--signal-2);
  color: var(--on-signal);
}

.btn--ghost {
  background-color: transparent;
  border-color: var(--rule-2);
  color: var(--fg);
}

.btn--ghost:hover {
  border-color: var(--fg);
  color: var(--fg);
}

/* For ink surfaces: solid paper button. */
.btn--inv {
  background-color: var(--paper);
  border-color: var(--paper);
  color: var(--ink);
}

.btn--inv:hover {
  background-color: var(--card);
  border-color: var(--card);
  color: var(--ink);
}

.btn[aria-disabled="true"],
.btn:disabled {
  opacity: .45;
  cursor: not-allowed;
}


/* == 08 Eyebrow, decorative bracket, highlight ============================ */

.eyebrow {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-mono);
  line-height: 1.4;
  color: var(--fg-2);
}

.eyebrow__mark {
  color: var(--mark);
  white-space: nowrap;
  margin-right: .5em;
}

/* --- Section label -------------------------------------------------------
   Replaces the bracketed [ 01 ] eyebrow everywhere the content is not a
   sequence. Numbering a set of sections that has no order is decoration
   pretending to be structure; a plain uppercase label says the same thing
   without the false claim. The bracket form survives only where order is
   real information — the process steps.
   ------------------------------------------------------------------------ */

.label {
  display: flex;
  align-items: center;
  gap: .75em;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-label);
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--fg-2);
}

/* A short rule leading into the label, in the section's accent. */
.label::before {
  content: '';
  flex: none;
  width: 28px;
  height: 2px;
  background-color: var(--accent);
}

.label--flag::before {
  background-color: var(--flag);
}

/* Oversized hairline bracket used once per page as decoration. */
.deco-bracket {
  position: absolute;
  pointer-events: none;
  color: var(--mark);
  opacity: .16;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.5;
}

/* Faint oversized [t] mark, ink sections only. */
.deco-mark {
  position: absolute;
  pointer-events: none;
  opacity: .05;
  width: clamp(280px, 40vw, 560px);
}

.deco-mark--right {
  top: 50%;
  right: calc(var(--gutter) * -1);
  transform: translateY(-50%);
}

.deco-mark--left {
  top: 50%;
  left: calc(var(--gutter) * -1);
  transform: translateY(-50%);
}

/* The single acid highlight allowed inside an ink section. */
.hl {
  color: var(--acid);
}

.hl--underline {
  color: inherit;
  box-shadow: inset 0 -.12em 0 var(--acid);
}


/* == 09 Nav =============================================================== */

.nav {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background-color: var(--paper);
  border-bottom: 1px solid var(--line);
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--nav-h);
}

.nav__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  text-decoration: none;
  color: var(--text);
  flex: none;
}

.nav__brand:hover {
  color: var(--text);
}

.logo-mark {
  width: 34px;
  height: 34px;
  flex: none;
}

/* The mark ships off-white for dark backgrounds; make it ink on paper. */
.logo-mark--ink {
  filter: brightness(0);
}

.nav__wordmark {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-display);
  line-height: 1;
  white-space: nowrap;
}

.nav__wordmark-2 {
  font-weight: var(--fw-medium);
  color: var(--text-2);
}

.nav__links {
  display: none;
  align-items: center;
  gap: var(--space-4);
}

.nav__link {
  position: relative;
  display: inline-block;
  padding-block: var(--space-1);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-2);
  text-decoration: none;
}

.nav__link:hover {
  color: var(--text);
}

.nav__link[aria-current="page"] {
  color: var(--text);
  font-weight: var(--fw-semibold);
}

/* One underline for every link: it sweeps in from the left on hover and
   simply stays put for the page you are on. */
.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  background-color: var(--signal);
  transform: scaleX(0);
  transform-origin: left center;
}

.nav__link:hover::after,
.nav__link[aria-current="page"]::after {
  transform: scaleX(1);
}

.nav__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: none;
}

/* The nav CTA's full label is too wide beside the brand and the burger on a
   phone. Below 600px it shows "Free concept"; the full label stays in the
   accessibility tree (clipped, not removed), so the link's name never
   changes. */
.nav__cta-short {
  display: none;
}

@media (max-width: 599px) {
  .nav__cta-full {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .nav__cta-short {
    display: inline;
  }
}

.nav__burger {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-right: calc(var(--space-1) * -1);
  border-radius: var(--r-sm);
  color: var(--text);
}

.nav__burger-icon {
  width: 24px;
  height: 24px;
  stroke: currentColor;
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: round;
}

/* The burger only exists when JS can open the overlay it controls. */
.js .nav__burger {
  display: inline-flex;
}

/* No-JS fallback: the bar links wrap under the brand instead. */
html:not(.js) .nav__inner {
  flex-wrap: wrap;
  row-gap: var(--space-1);
  padding-block: var(--space-2);
}

html:not(.js) .nav__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  order: 3;
  width: 100%;
}

/* Below 420px the bar runs out of room: brand + "Book a call" + burger no
   longer fit inside the gutters, and the header pushed the page sideways by
   9px. The CTA stays — it is the point of the site — but tightens up. */
@media (max-width: 419px) {
  .nav__inner {
    gap: var(--space-2);
  }

  /* Under 400px the wordmark has to go, but the logo image is alt="" — the
     wordmark IS the brand link's accessible name. So it is clipped rather
     than removed: gone from the layout, still read by a screen reader. */
  .nav__wordmark {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .nav .btn--primary {
    padding-inline: var(--space-2);
    font-size: var(--fs-xs);
  }

  .nav__actions {
    gap: var(--space-1);
  }
}

@media (min-width: 901px) {
  .nav__links {
    display: flex;
  }

  .js .nav__burger {
    display: none;
  }

  html:not(.js) .nav__inner {
    flex-wrap: nowrap;
    row-gap: 0;
    padding-block: 0;
  }

  html:not(.js) .nav__links {
    order: 0;
    width: auto;
  }
}

/* --- Full-screen ink overlay menu --- */

.nav__overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background-color: var(--ink);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.nav__overlay-inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-height: 100%;
  padding-block: var(--space-3) var(--space-6);
}

.nav__overlay-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: calc(var(--nav-h) - var(--space-3));
}

.nav__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-right: calc(var(--space-1) * -1);
  border-radius: var(--r-sm);
  color: var(--text-inv);
}

.nav__close-icon {
  width: 24px;
  height: 24px;
  stroke: currentColor;
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: round;
}

.nav__menu {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--rule);
}

.nav__menu-link {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--rule);
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-display);
  line-height: 1.1;
  color: var(--text-inv);
  text-decoration: none;
}

.nav__menu-link:hover {
  color: var(--text-inv);
}

.nav__menu-num {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-regular);
  letter-spacing: var(--tracking-mono);
  color: var(--text-inv-2);
}

.nav__menu-link[aria-current="page"] .nav__menu-num {
  color: var(--acid);
}

.nav__overlay-foot {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: auto;
}

.nav__overlay-contact {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.nav__overlay-contact a {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--text-inv);
  text-decoration: none;
}

.nav__overlay-contact a:hover {
  text-decoration: underline;
}

/* Scroll lock while the overlay is open. */
.is-nav-open,
.is-nav-open body {
  overflow: hidden;
}


/* == 10 Footer ============================================================ */

.footer {
  background-color: var(--ink);
  padding-block: var(--space-11) var(--space-6);
}

.footer__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
}

.footer__brand {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 42ch;
}

.footer__logo-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-display);
  color: var(--text-inv);
  text-decoration: none;
}

.footer__blurb {
  font-size: var(--fs-sm);
  color: var(--text-inv-2);
  line-height: 1.6;
}

.footer__meta {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--text-inv-2);
}

.footer__head {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-mono);
  color: var(--text-inv-2);
  margin-bottom: var(--space-3);
}

.footer__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.footer__list a {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-inv);
  text-decoration: none;
}

.footer__list a:hover {
  color: var(--text-inv);
  text-decoration: underline;
  text-underline-offset: .2em;
}

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: var(--space-9);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line-inv);
}

.footer__copy {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--text-inv-2);
}

.footer__legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.footer__legal a {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--text-inv-2);
  text-decoration: none;
}

.footer__legal a:hover {
  color: var(--text-inv);
}

.footer__sep {
  color: var(--text-inv-2);
  opacity: .5;
}

@media (min-width: 641px) {
  .footer__inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 901px) {
  .footer__inner {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-6);
  }
}


/* == 11 Card, pill ======================================================== */

.card {
  padding: var(--space-4);
  background-color: var(--surface-card);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
}

/* Flow, not flex: a bare .pill or .btn in a card keeps its natural width. */
.card > * + * {
  margin-top: var(--space-2);
}

.card--ink {
  background-color: var(--ink-2);
  border-color: var(--ink-3);
}

.card--flush {
  padding: 0;
  overflow: hidden;
}

.card__title {
  font-size: var(--fs-lg);
}

.card__body {
  font-size: var(--fs-sm);
  color: var(--fg-2);
  /* A card in a two-column grid is half the container wide, which on a
     2560px screen ran these to 109 characters a line. */
  max-width: var(--measure);
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  padding: 5px 12px;
  border: 1px solid var(--rule-2);
  border-radius: var(--r-pill);
  background-color: transparent;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  line-height: 1.4;
  color: var(--fg-2);
  white-space: nowrap;
}

.pill--solid {
  background-color: var(--surface-2);
  border-color: transparent;
  color: var(--fg);
}

.pill--signal {
  border-color: var(--signal);
  color: var(--signal);
}


/* == 12 Stat ============================================================== */

/* Gapless grid: the 1px gaps expose the rule colour as shared hairlines. */
.stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background-color: var(--rule);
  border-block: 1px solid var(--rule);
}

.stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-5) var(--space-3);
  background-color: var(--surface);
}

/* First cell of each row hugs the container edge. */
.stat:nth-child(2n + 1) {
  padding-left: 0;
}

.stat__value {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-display);
  letter-spacing: var(--tracking-hero);
  line-height: 1;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}

.stat--acid .stat__value {
  color: var(--acid);
}

/* A themed stat paints its number and carries a short accent rule above it. */
.stat--accent .stat__value {
  color: var(--accent);
}

.stat__rule {
  display: block;
  width: 28px;
  height: 3px;
  border-radius: var(--r-pill);
  background-color: var(--accent);
  transition: width var(--dur-slower) var(--ease);
}

.stat--accent {
  transition: background-color var(--dur-slow) var(--ease);
}

.stat--accent:hover {
  background-color: var(--accent-soft);
}

/* The rule runs the width of the cell on hover. */
.stat--accent:hover .stat__rule {
  width: 100%;
}

.stat__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  line-height: 1.4;
  color: var(--fg-2);
}

@media (min-width: 641px) {
  .stats {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .stat,
  .stat:nth-child(2n + 1) {
    padding-inline: var(--space-4);
  }

  .stat:nth-child(4n + 1) {
    padding-left: 0;
  }
}


/* == 13 Steps ============================================================= */

.steps {
  border-top: 1px solid var(--rule);
}

.steps__item {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-2);
  padding-block: var(--space-5);
  border-bottom: 1px solid var(--rule);
}

.steps__num {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--mark);
}

.steps__title {
  font-size: var(--fs-xl);
  margin-bottom: var(--space-1);
}

.steps__body {
  font-size: var(--fs-sm);
  color: var(--fg-2);
  max-width: var(--measure);
}

/* --- Accent variant: a numbered badge and a glyph per step -------------
   Used on the home page, where the process section otherwise runs as three
   unbroken blocks of type.                                              */

.steps--accent .steps__item {
  position: relative;
}

.steps__badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: none;
  border-radius: var(--r-pill);
  background-color: var(--accent-soft-2);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  letter-spacing: var(--tracking-mono);
  transition:
    background-color var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

.steps__item:hover .steps__badge {
  background-color: var(--accent);
  color: var(--on-signal);
}

.steps__glyph {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.steps__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

/* The title already carries the gap below the head. */
.steps__head .steps__title {
  margin-bottom: 0;
}

.steps--accent .steps__num {
  color: var(--accent);
}

/* The accent edge sits on the item's own top rule. */
.steps--accent .steps__item::before {
  content: '';
  position: absolute;
  top: -1px;
  left: 0;
  width: 0;
  height: 2px;
  background-color: var(--accent);
  transition: width var(--dur-slower) var(--ease);
}

.steps--accent .steps__item:hover::before {
  width: 100%;
}

@media (min-width: 901px) {
  .steps__item {
    grid-template-columns: 140px minmax(0, 1fr);
    column-gap: var(--space-6);
    padding-block: var(--space-6);
  }

  /* Three abreast instead of stacked rows. */
  .steps--cols {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: var(--space-6);
    border-top: 0;
  }

  .steps--cols .steps__item {
    grid-template-columns: minmax(0, 1fr);
    border-top: 1px solid var(--rule);
    border-bottom: 0;
    padding-block: var(--space-4) 0;
  }

  /* In a third of the width the badge and the title cannot share a line —
     the title wraps to three. Stack them. */
  .steps--cols .steps__head {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
  }
}


/* == 14 Editorial rows ==================================================== */

.rows {
  border-top: 1px solid var(--rule);
}

.rows__item {
  border-bottom: 1px solid var(--rule);
}

.rows__link {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding-block: var(--space-5);
  text-decoration: none;
  color: var(--fg);
}

.rows__link:hover {
  color: var(--fg);
}

/* Not a number. Three services are a set, not a sequence, so this names the
   discipline instead of counting it — and the swatch ties the row to the
   colour it uses further down the page. */
.rows__kind {
  grid-column: 1 / -1;
  display: inline-flex;
  align-items: center;
  gap: .7em;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--accent);
}

.rows__main {
  display: grid;
  gap: var(--space-2);
}

.rows__title {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-display);
  line-height: 1;
}

.rows__body {
  font-size: var(--fs-md);
  color: var(--fg-2);
  max-width: 52ch;
}

.rows__tags {
  --cluster-space: var(--space-1);
}

.rows__go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border: 1px solid var(--rule-2);
  border-radius: var(--r-pill);
  color: var(--fg);
  flex: none;
}

.rows__link:hover .rows__go {
  background-color: var(--accent);
  border-color: var(--accent);
  color: var(--on-signal);
}

/* A colour edge that grows out of the rule on hover, so the row reads as
   belonging to its accent rather than merely being tinted by it. */
.rows__item {
  position: relative;
  --rows-edge: 0%;
  transition: background-color var(--dur) var(--ease);
}

.rows__item::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 2px;
  width: var(--rows-edge);
  background-color: var(--accent);
  transition: width var(--dur-slower) var(--ease);
}

.rows__item:hover,
.rows__item:focus-within {
  --rows-edge: 100%;
  background-color: var(--accent-soft);
}

.rows__link:hover .rows__title,
.rows__link:focus-visible .rows__title {
  color: var(--accent);
}

.rows__title {
  transition: color var(--dur) var(--ease);
}

/* Thumbnail of the work behind each service. Hidden on narrow screens,
   where the row is already tall enough. */
.rows__media {
  display: none;
}

/* 1240px, not 1101px. The diagram column takes 220px out of the row, and at
   1101px that left the title track 188px — narrower than the word "Hosting"
   at display size, so body's `overflow-wrap: break-word` safety net force-broke
   it mid-letter ("Hostin / g"). Measured: the break clears at 1190px. 1240
   leaves headroom. Below this the row is text-only, which is how it read
   before the diagrams existed. */
@media (min-width: 1240px) {
  .rows__media {
    display: block;
    /* Wider than the screenshots this slot used to hold: a line drawing
       needs the room to stay legible where a screenshot only had to read
       as a shape. */
    width: 220px;
    flex: none;
    border-radius: var(--r-md);
    overflow: hidden;
    border: 1px solid var(--rule);
    background-color: var(--accent-soft);
    /* These are flat diagrams on the page's own ground, not photographs, so
       they need no desaturating at rest — only the lift on hover. */
    transition: transform var(--dur-slow) var(--ease);
  }

  .rows__item:hover .rows__media,
  .rows__item:focus-within .rows__media {
    transform: translateY(-4px);
  }

  /* contain, not cover: the diagram is drawn at exactly this ratio and must
     never be cropped — a clipped edge on a line drawing looks like a bug. */
  .rows__media img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 10;
    object-fit: contain;
  }
}

@media (min-width: 901px) {
  .rows__link {
    grid-template-columns: 140px minmax(0, 1fr) auto;
    align-items: start;
    column-gap: var(--space-6);
    padding-block: var(--space-7);
  }

  /* From 1240px a fourth column opens for the diagram. The row stays
     top-aligned — only the diagram and the arrow centre themselves. */
  @media (min-width: 1240px) {
    .rows__link {
      grid-template-columns: 140px minmax(0, 1fr) 220px auto;
    }

    .rows__media,
    .rows__go {
      align-self: center;
    }
  }

  .rows__kind {
    grid-column: auto;
    padding-top: .7em;
  }

  .rows__go {
    align-self: center;
  }

  .rows__main {
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    column-gap: var(--space-6);
    align-items: start;
  }

  .rows__title {
    grid-column: 1;
    grid-row: 1 / span 2;
  }

  .rows__body {
    grid-column: 2;
    grid-row: 1;
  }

  .rows__tags {
    grid-column: 2;
    grid-row: 2;
  }
}


/* == 15 Checklist ========================================================= */

.checklist {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  border-top: 1px solid var(--rule);
}

.checklist__item {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr);
  align-items: start;
  gap: var(--space-2);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--rule);
  font-size: var(--fs-md);
  color: var(--fg);
}

.checklist__icon {
  width: 22px;
  height: 22px;
  margin-top: 2px;
  stroke: var(--accent);
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (min-width: 901px) {
  .checklist--2col {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-6);
  }
}


/* == 16 Accordion ========================================================= */

.accordion {
  border-top: 1px solid var(--rule);
}

.accordion__item {
  border-bottom: 1px solid var(--rule);
}

.accordion__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-block: var(--space-3);
  list-style: none;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.25;
  color: var(--fg);
}

.accordion__head::-webkit-details-marker {
  display: none;
}

.accordion__head:hover {
  color: var(--link);
}

.accordion__chev {
  width: 22px;
  height: 22px;
  flex: none;
  stroke: currentColor;
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* grid-template-rows 0fr → 1fr is the height-animation trick. */
.accordion__panel {
  display: grid;
  grid-template-rows: 0fr;
}

.accordion__item[open] .accordion__panel {
  grid-template-rows: 1fr;
}

.accordion__panel-inner {
  overflow: hidden;
  min-height: 0;
}

.accordion__body {
  padding-bottom: var(--space-4);
  font-size: var(--fs-md);
  color: var(--fg-2);
  max-width: var(--measure);
}

.accordion__item[open] .accordion__chev {
  transform: rotate(180deg);
}


/* == 17 Form field ======================================================== */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.field__label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-mono);
  color: var(--fg);
}

.field__req {
  color: var(--mark);
}

.field__optional {
  color: var(--fg-2);
}

.field__control {
  position: relative;
  display: block;
}

.field__input {
  width: 100%;
  padding: 13px var(--space-2);
  background-color: var(--surface-card);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-sm);
  font-family: var(--font-text);
  font-size: var(--fs-md);
  line-height: 1.4;
  color: var(--fg);
}

.field__input::placeholder {
  color: var(--fg-2);
  opacity: 1;
}

textarea.field__input {
  min-height: 140px;
  line-height: 1.6;
}

select.field__input {
  appearance: none;
  padding-right: var(--space-6);
  cursor: pointer;
}

/* Chevron for .field--select (needs the .field__control wrapper). */
.field--select .field__control::after {
  content: "";
  position: absolute;
  top: 50%;
  right: var(--space-3);
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--fg-2);
  border-bottom: 2px solid var(--fg-2);
  transform: translateY(-75%) rotate(45deg);
  pointer-events: none;
}

.field__hint {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--fg-2);
}

.field__error {
  display: flex;
  align-items: baseline;
  gap: .5em;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-mono);
  color: var(--fg);
}

.field__error-mark {
  color: var(--mark);
}

/* Invalid state: 2px ink edge + the [ ! ] error line. Distinct from the
   signal-coloured focus ring, and never colour-alone. */
.field--invalid .field__input {
  border-width: 2px;
  border-color: var(--fg);
}

.field__input:disabled {
  background-color: var(--surface-2);
  color: var(--fg-2);
  cursor: not-allowed;
}

/* Checkbox / radio row. */
.field--check {
  flex-direction: row;
  align-items: start;
  gap: var(--space-2);
}

.field--check .field__label {
  font-family: var(--font-text);
  font-size: var(--fs-sm);
  letter-spacing: normal;
  color: var(--fg-2);
}

.field--check input {
  width: 20px;
  height: 20px;
  margin-top: 2px;
  accent-color: var(--signal);
  flex: none;
}


/* == 18 Notice ============================================================ */

.notice {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background-color: var(--surface-2);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--fg);
}

.notice__mark {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  line-height: 1.9;
  color: var(--mark);
}

/* The notice's text column is a 1fr grid track, so on a wide screen it runs
   as long as the container allows — 123 characters at 2560px. */
.notice > div,
.notice p {
  max-width: var(--measure);
}

.notice__title {
  font-family: var(--font-text);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-tight);
  margin-bottom: 2px;
  color: var(--fg);
}

.notice a {
  color: inherit;
}

/* Errors invert to ink with an acid mark — impossible to miss, still on
   palette (acid appears only on a dark surface). */
.notice--error {
  background-color: var(--ink);
  border-color: var(--ink);
}

.notice--error .notice__mark {
  color: var(--acid);
}

.notice--success {
  background-color: var(--signal);
  border-color: var(--signal);
  color: var(--on-signal);
}

.notice--success .notice__mark,
.notice--success .notice__title {
  color: var(--on-signal);
}


/* == 19 Table ============================================================= */

.table-wrap {
  width: 100%;
  overflow-x: auto;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  scrollbar-width: thin;
  scrollbar-color: var(--rule-2) transparent;
}

.table {
  width: 100%;
  min-width: 720px;
  font-size: var(--fs-sm);
  text-align: left;
}

.table caption {
  padding: var(--space-2) var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--fg-2);
  text-align: left;
}

.table th {
  padding: var(--space-2) var(--space-3);
  background-color: var(--surface-2);
  border-bottom: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-mono);
  color: var(--fg-2);
  white-space: nowrap;
}

.table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
  color: var(--fg);
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

.table__meta {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--fg-2);
}

.table__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}


/* == 20 Badge ============================================================= */

.badge {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  padding: 3px 10px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-mono);
  line-height: 1.5;
  white-space: nowrap;
}

.badge--pending {
  background-color: var(--surface-2);
  border-color: var(--rule-2);
  color: var(--fg-2);
}

.badge--emailed {
  background-color: transparent;
  border-color: var(--signal);
  color: var(--signal);
}

.badge--approved {
  background-color: var(--signal);
  border-color: var(--signal);
  color: var(--on-signal);
}

.badge--cancelled {
  background-color: transparent;
  border-color: var(--rule-2);
  color: var(--fg-2);
  text-decoration: line-through;
}

.badge--failed {
  background-color: var(--ink);
  border-color: var(--ink);
  color: var(--text-inv);
}


/* == 21 Marquee =========================================================== */

.marquee {
  overflow: hidden;
  border-block: 1px solid var(--rule);
  padding-block: var(--space-3);
}

.marquee__track {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  width: max-content;
}

.marquee__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  letter-spacing: var(--tracking-mono);
  color: var(--fg-2);
  white-space: nowrap;
}

.marquee__sep {
  color: var(--mark);
}


/* == 22 Frame (browser chrome) + frame stack ============================== */

.frame {
  display: flex;
  flex-direction: column;
  background-color: var(--surface-card);
  border: 1px solid var(--rule-2);
  border-radius: var(--r-md);
  overflow: hidden;
}

.frame__bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 8px var(--space-2);
  background-color: var(--surface-2);
  border-bottom: 1px solid var(--rule);
}

.frame__dots {
  display: inline-flex;
  gap: 5px;
  flex: none;
}

.frame__dots span {
  width: 8px;
  height: 8px;
  border-radius: var(--r-pill);
  background-color: var(--rule-2);
}

.frame__url {
  flex: 1 1 auto;
  min-width: 0;
  padding: 3px 10px;
  background-color: var(--surface-card);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: var(--tracking-mono);
  color: var(--fg-2);
  line-height: 1.6;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.frame__img {
  display: block;
  width: 100%;
  aspect-ratio: 1440 / 900;
  object-fit: cover;
  object-position: top center;
  background-color: var(--surface-2);
}

.frame--tall .frame__img {
  aspect-ratio: 4 / 3;
}

/* Layered composition of 2–3 frames (hero, portfolio intro). */
.frame-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.frame-stack__item--2,
.frame-stack__item--3 {
  display: none;
}

@media (min-width: 901px) {
  .frame-stack {
    display: block;
    position: relative;
    aspect-ratio: 1 / .6;
  }

  .frame-stack__item {
    position: absolute;
  }

  .frame-stack__item--1 {
    top: 14%;
    left: 0;
    width: 74%;
    z-index: 2;
  }

  .frame-stack__item--2 {
    display: flex;
    top: 0;
    right: 0;
    width: 56%;
    z-index: 1;
  }

  .frame-stack__item--3 {
    display: flex;
    right: 0;
    bottom: 0;
    width: 40%;
    z-index: 3;
  }
}


/* == 23 Portfolio carousel ================================================ */

.pf-carousel {
  position: relative;
}

.pf-carousel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.pf-nav {
  display: flex;
  gap: var(--space-1);
}

.pf-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border: 1px solid var(--rule-2);
  border-radius: var(--r-pill);
  background-color: transparent;
  color: var(--fg);
}

.pf-btn:hover {
  background-color: var(--signal);
  border-color: var(--signal);
  color: var(--on-signal);
}

.pf-btn:disabled {
  opacity: .3;
  cursor: not-allowed;
  background-color: transparent;
  border-color: var(--rule-2);
  color: var(--fg);
}

.pf-btn__icon {
  width: 20px;
  height: 20px;
  stroke: currentColor;
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.pf-track {
  display: flex;
  gap: var(--space-3);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline-start: 0;
  /* The cards sit off to the right of this box. Scrolling clips them, but
     their overflow still counted toward the scrollable width of every
     ancestor, which let the whole page be dragged sideways on a wide screen.
     Paint containment keeps that overflow to this element. */
  contain: paint;
  /* Room above for the card's hover lift, which the containment would
     otherwise cut off. */
  padding-block: 4px var(--space-2);
  /* No scrollbar chrome. The track is still scrollable, swipeable and
     keyboard-scrollable — only the bar itself is hidden. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  overscroll-behavior-x: contain;
}

.pf-track::-webkit-scrollbar {
  display: none;
}

/* Whole cards only. The basis divides the track exactly, so a card never
   ends up straddling the right edge with its content chopped: one card at
   phone width, two from 640px, and a grid of three from 1024px. */
.pf-card {
  flex: 0 0 100%;
  /* Without this the flex item's automatic minimum size (the screenshot's
     intrinsic width) overrides the basis and the cards blow past the track. */
  min-width: 0;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  /* The containing block for the stretched link below. */
  position: relative;
  transition: transform var(--dur-slow) var(--ease);
}

/* The whole card opens the demo. The stretched-link pattern rather than an
   <a> around the card: wrapping would nest this anchor inside another one,
   which is invalid and breaks the accessible name. The visible "Open live
   demo" link is still the only anchor — it just claims the card's area.
   Everything else in the card is non-interactive, so nothing is swallowed. */
.pf-card__link::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: var(--z-raised);
  border-radius: var(--r-lg);
}

.pf-card:hover,
.pf-card:focus-within {
  transform: translateY(-3px);
}

/* The lift is decoration; the card must not move for anyone who asked it not to. */
@media (prefers-reduced-motion: reduce) {
  .pf-card,
  .pf-card:hover,
  .pf-card:focus-within {
    transform: none;
    transition: none;
  }
}

@media (min-width: 640px) {
  .pf-card {
    flex-basis: calc((100% - var(--space-3)) / 2);
  }
}

.pf-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.pf-card__title {
  font-size: var(--fs-lg);
  letter-spacing: var(--tracking-tight);
}

.pf-card__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid var(--rule-2);
  padding-bottom: 2px;
}

/* The card's frame takes a tint of its industry colour, so a glance down
   the carousel reads as five different practices rather than five greys. */
.pf-card .frame {
  background-color: var(--accent-soft);
  transition:
    border-color var(--dur-slow) var(--ease),
    transform var(--dur-slow) var(--ease);
}

.pf-card:hover .frame {
  border-color: var(--accent);
}

.pf-card__link:hover {
  border-bottom-color: currentColor;
}

/* --- Desktop: a grid, not a scroller ------------------------------------
   Five cards will not fit across one row at a readable size, so they lay
   out three-and-two. Nothing scrolls, which is why the paging buttons and
   the scroll-progress hairline are hidden here rather than sitting dead —
   site.js already disables the buttons when there is nothing to scroll.
   ------------------------------------------------------------------------ */

@media (min-width: 1024px) {
  .pf-track {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-4);
    overflow-x: visible;
    scroll-snap-type: none;
    /* Nothing overflows now, and containment would clip the hover lift. */
    contain: none;
    padding-block: 4px;
  }

  .pf-card {
    /* Inert inside a grid container, but reset so the intent is readable. */
    flex: initial;
  }

  .pf-carousel .pf-nav,
  .pf-carousel .pf-progress {
    display: none;
  }
}

.pf-carousel__foot {
  margin-top: var(--space-4);
}


/* == 24 Hero & page head ================================================== */

.hero {
  position: relative;
  padding-block: var(--space-7) var(--space-8);
  isolation: isolate;
}

.hero__title {
  font-size: var(--fs-hero);
  line-height: var(--lh-hero);
  letter-spacing: var(--tracking-hero);
  /* The widest setting on the page. The hero is the one place the width
     axis is pushed all the way, which is what makes it read as a sign. */
  font-stretch: 122%;
  font-variation-settings: 'wdth' var(--wdth-wide);
  font-weight: var(--fw-display);
  max-width: 17ch;
}

.hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-7) var(--grid-gap-x);
  align-items: start;
}

.hero__content {
  position: relative;
  z-index: var(--z-raised);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  align-items: start;
}

.hero__sub {
  font-size: var(--fs-lg);
  line-height: 1.5;
  color: var(--text-2);
  max-width: 46ch;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* The primary CTA label is long; on a phone both actions take the full
   width so the pair reads as a clear stack instead of a ragged wrap. */
@media (max-width: 480px) {
  .hero__actions .btn,
  .cta-band .cluster .btn {
    flex: 1 1 100%;
    justify-content: center;
  }
}

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--line);
  width: 100%;
  max-width: 46ch;
}

.hero__visual {
  position: relative;
  min-width: 0;
}

@media (min-width: 981px) {
  .hero__grid {
    grid-template-columns: minmax(0, 46fr) minmax(0, 54fr);
    gap: var(--space-8);
    align-items: center;
  }
}


/* --- The source panel ---------------------------------------------------
   The page showing its own markup. Ink panel, mono, one row per line. Each
   row that maps to something rendered is a <button>, so the whole thing is
   operable from the keyboard and announces itself as interactive; the two
   structural lines are plain <li>s because they light nothing.
   ------------------------------------------------------------------------ */

.src {
  margin: 0;
  background-color: var(--ink);
  color: var(--text-inv);
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--ink-3);
}

.src__bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 10px var(--space-3);
  border-bottom: 1px solid var(--ink-3);
  background-color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
}

.src__file {
  color: var(--text-inv);
}

.src__note {
  color: var(--text-inv-2);
  text-align: right;
}

.src__code {
  counter-reset: srcline;
  list-style: none;
  margin: 0;
  padding: var(--space-2) 0;
  /* Long lines scroll inside the panel rather than widening the page. */
  overflow-x: auto;
}

.src__code > li {
  counter-increment: srcline;
}

.src__line {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  width: 100%;
  padding: 3px var(--space-3);
  border: 0;
  background-color: transparent;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.85;
  color: var(--text-inv);
  text-align: left;
  white-space: pre;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease);
}

/* The line number, drawn from the counter so the markup carries no numbers
   of its own. */
.src__line::before {
  content: counter(srcline);
  flex: none;
  width: 2ch;
  text-align: right;
  color: var(--text-inv-2);
  opacity: .7;
}

.src__line--plain {
  cursor: default;
  color: var(--text-inv-2);
}

.src__line code {
  font-size: inherit;
}

/* Lit — by hover, by focus, or by pointing at the rendered element. Yellow
   is the flag colour and this is exactly what it is for: marking the thing
   you are currently looking at. */
.src__line:hover,
.src__line:focus-visible,
.src__line.is-lit {
  background-color: var(--flag);
  color: var(--on-flag);
}

.src__line:hover::before,
.src__line:focus-visible::before,
.src__line.is-lit::before {
  color: var(--on-flag);
  opacity: .55;
}

.src__hint {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  padding: 10px var(--space-3);
  border-top: 1px solid var(--ink-3);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--text-inv-2);
}

.src__hint-key {
  flex: none;
  padding: 1px 7px;
  border: 1px solid var(--ink-3);
  border-radius: var(--r-sm);
  color: var(--text-inv);
}

/* The rendered counterpart, while its line is lit. An outline rather than a
   background, so nothing under it moves or changes contrast. */
[data-src].is-lit {
  outline: 2px solid var(--flag);
  outline-offset: 6px;
  border-radius: var(--r-sm);
}

/* Interior page header (services, about, faq, legal…). */
.page-head {
  padding-block: var(--space-9) var(--space-8);
  border-bottom: 1px solid var(--line);
}

.page-head__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.page-head__title {
  font-size: var(--fs-3xl);
  max-width: 19ch;
}


/* == 25 CTA band ========================================================== */

.cta-band {
  position: relative;
  background-color: var(--ink);
  padding-block: var(--space-section);
  overflow: hidden;
}

/* The flag, once more, as the top edge of the closing band. It runs on every
   page, so the eye meets the same colour at the same moment each time. */
.cta-band::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 4px;
  background-color: var(--flag);
}

.cta-band__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  align-items: start;
  position: relative;
  z-index: var(--z-raised);
}

.cta-band__title {
  font-size: var(--fs-2xl);
  max-width: 16ch;
}

.cta-band__sub {
  font-size: var(--fs-lg);
  color: var(--text-inv-2);
  max-width: 52ch;
}

.cta-band__contact {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--line-inv);
  width: 100%;
}

.cta-band__contact a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  letter-spacing: var(--tracking-mono);
  color: var(--text-inv);
  text-decoration: none;
}

.cta-band__contact a:hover {
  color: var(--text-inv);
  text-decoration: underline;
  text-underline-offset: .2em;
}


/* == 26 Legal prose ======================================================= */

.legal {
  max-width: var(--measure);
  font-size: var(--fs-md);
  line-height: 1.7;
  color: var(--text);
}

.legal > * + * {
  margin-top: var(--space-3);
}

.legal h2 {
  font-size: var(--fs-xl);
  margin-top: var(--space-8);
}

.legal h3 {
  font-size: var(--fs-lg);
  margin-top: var(--space-6);
}

.legal ul,
.legal ol {
  padding-left: var(--space-3);
}

.legal li + li {
  margin-top: var(--space-1);
}

.legal a {
  color: var(--signal);
}

.legal dl {
  display: grid;
  gap: var(--space-1);
}

.legal dt {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--text-2);
}

.legal dd {
  margin: 0;
}

.legal__updated {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--text-2);
}

.legal blockquote {
  padding-left: var(--space-3);
  border-left: 1px solid var(--line-2);
  color: var(--text-2);
}


/* == 27 Focus ============================================================= */

:focus-visible {
  outline: var(--focus-width) solid var(--ring);
  outline-offset: var(--focus-offset);
}

/* Scroll containers get a tighter offset so the ring stays inside. */
.pf-track:focus-visible {
  outline-offset: 2px;
}


/* == 27b Scroll progress, back-to-top, copy chip, cursor badge ============
   All four are injected by assets/js/site.js. Nothing here is required for
   the page to work; each is an affordance layered on top.
   ======================================================================== */

/* Reading progress: a hairline across the very top of the viewport, above
   the sticky header and below the mobile menu. */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: calc(var(--z-sticky) + 1);
  pointer-events: none;
}

.scroll-progress__bar {
  height: 100%;
  width: 100%;
  background-color: var(--signal);
  transform: scaleX(0);
  transform-origin: left center;
}

/* Back to top. visibility (not display) so it can animate and still leave
   the tab order while it is hidden. */
.to-top {
  position: fixed;
  right: var(--gutter);
  bottom: var(--gutter);
  z-index: var(--z-sticky);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background-color: var(--paper);
  color: var(--text);
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
}

.to-top.is-visible {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.to-top:hover {
  background-color: var(--ink);
  border-color: var(--ink);
  color: var(--text-inv);
}

.to-top__icon {
  width: 20px;
  height: 20px;
  stroke: currentColor;
  stroke-width: 1.5;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Copy-the-address chip, added beside the email in every closing band. */
.copy-chip {
  padding: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--fg-2);
  border-bottom: 1px solid var(--rule);
}

.copy-chip:hover {
  color: var(--fg);
}

.copy-chip.is-done {
  color: var(--acid);
  border-bottom-color: transparent;
}

/* Follows the pointer across a portfolio image to say it opens. */
.cursor-badge {
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--z-sticky);
  display: none;
  align-items: center;
  gap: .5em;
  padding: 7px 14px;
  border-radius: var(--r-pill);
  background-color: var(--signal);
  color: var(--on-signal);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
}

.cursor-badge.is-on {
  display: inline-flex;
}

/* Mouse-only link wrapped around a portfolio screenshot by site.js. It is
   aria-hidden and unfocusable, so the visible text link stays the single
   thing a keyboard or screen reader sees. */
/* Sits ABOVE .pf-card__link's stretched ::after, which otherwise covers the
   screenshot and swallows every pointer event over it — killing both the
   image push-in below and the cursor badge motion.js binds to this element.
   Raising it costs nothing: .pf-media wraps the same href as the stretched
   link, so whichever one receives the click navigates identically, and the
   rest of the card is still covered by the stretched link. */
.pf-media {
  position: relative;
  z-index: calc(var(--z-raised) + 1);
  display: block;
  border-radius: var(--r-md);
  text-decoration: none;
  color: inherit;
}

.pf-media:hover {
  color: inherit;
}

/* The badge accompanies the pointer rather than replacing it — hiding the
   system cursor costs more than the effect is worth for anyone who tracks
   it. The picture answers the hover with a small push-in instead. */
@media (hover: hover) and (pointer: fine) {
  .pf-media:hover .frame__img {
    transform: scale(1.03);
  }
}


/* == 28 Motion ============================================================
   Every transition and animation in the site lives in this block, so
   reduced-motion is honoured by construction. The animation pass (GSAP)
   layers scroll motion on top of the data-* hooks in the HTML.
   ======================================================================== */

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }

  .skip-link {
    transition: transform var(--dur) var(--ease);
  }

  .nav {
    transition: transform var(--dur-slow) var(--ease), box-shadow var(--dur-slow) var(--ease);
  }

  .btn,
  .pf-btn,
  .rows__go,
  .badge,
  .pill {
    transition:
      background-color var(--dur) var(--ease),
      border-color var(--dur) var(--ease),
      color var(--dur) var(--ease);
  }

  .btn .arrow,
  .link .arrow {
    transition: transform var(--dur) var(--ease);
  }

  .btn:hover .arrow,
  .link:hover .arrow {
    transform: translateX(3px);
  }

  .nav__link,
  .nav__menu-link,
  .nav__burger,
  .nav__close,
  .footer__list a,
  .footer__legal a,
  .accordion__head,
  .pf-card__link,
  .cta-band__contact a,
  .rows__link {
    transition: color var(--dur) var(--ease);
  }

  .link {
    transition: border-color var(--dur) var(--ease);
  }

  .nav__link::after {
    transition: transform var(--dur-slow) var(--ease);
  }

  .to-top {
    transition:
      opacity var(--dur-slow) var(--ease),
      transform var(--dur-slow) var(--ease),
      visibility var(--dur-slow) var(--ease),
      background-color var(--dur) var(--ease),
      border-color var(--dur) var(--ease),
      color var(--dur) var(--ease);
  }

  .to-top:hover {
    transform: translateY(-2px);
  }

  .copy-chip {
    transition: color var(--dur) var(--ease);
  }

  /* The bar follows the scrollbar directly, so it only needs to smooth the
     gap between animation frames. */
  .scroll-progress__bar {
    transition: transform var(--dur-fast) linear;
  }

  .accordion__panel {
    transition: grid-template-rows var(--dur-slow) var(--ease);
  }

  .accordion__chev {
    transition: transform var(--dur-slow) var(--ease);
  }

  .field__input {
    transition:
      border-color var(--dur) var(--ease),
      background-color var(--dur) var(--ease);
  }

  .pf-card .frame {
    transition: transform var(--dur-slow) var(--ease);
  }

  .pf-media .frame__img {
    transition: transform var(--dur-slower) var(--ease);
  }

  .pf-card:hover .frame {
    transform: translateY(-4px);
  }

  /* The overlay is toggled with [hidden]; a keyframe animation runs on
     first paint, which a transition could not do from display:none. */
  .nav__overlay:not([hidden]) .nav__menu-link,
  .nav__overlay:not([hidden]) .nav__overlay-foot {
    animation: textum-menu-in var(--dur-slow) var(--ease) both;
  }

  .nav__overlay:not([hidden]) .nav__menu-link:nth-child(2) { animation-delay: 40ms; }
  .nav__overlay:not([hidden]) .nav__menu-link:nth-child(3) { animation-delay: 80ms; }
  .nav__overlay:not([hidden]) .nav__menu-link:nth-child(4) { animation-delay: 120ms; }
  .nav__overlay:not([hidden]) .nav__menu-link:nth-child(5) { animation-delay: 160ms; }
  .nav__overlay:not([hidden]) .nav__overlay-foot { animation-delay: 200ms; }

  .accordion__item[open] .accordion__body {
    animation: textum-panel-in var(--dur-slow) var(--ease) both;
  }
}

@keyframes textum-menu-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes textum-panel-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}


/* == 28b Motion-layer hooks (assets/js/motion.js) =========================
   Classes toggled by the GSAP layer. Harmless when the script is absent.
   ======================================================================== */

/* Header slides away on scroll-down, returns on scroll-up; never while the
   menu is open or anything inside it has focus. */
.nav--hidden:not(:focus-within) {
  transform: translateY(-100%);
}

.nav--scrolled {
  box-shadow: 0 1px 0 var(--line);
}

/* Mouse drag on the portfolio track (touch keeps native scroll-snap). */
.pf-track.is-draggable {
  cursor: grab;
}

.pf-track.is-dragging {
  cursor: grabbing;
  scroll-snap-type: none;
  user-select: none;
  -webkit-user-select: none;
}

.pf-track.is-dragging a,
.pf-track.is-dragging img {
  pointer-events: none;
}

/* Scroll-progress hairline injected under the track. */
.pf-progress {
  height: 2px;
  margin-top: var(--space-2);
  background-color: var(--rule);
  overflow: hidden;
}

.pf-progress__bar {
  height: 100%;
  width: 100%;
  background-color: var(--signal);
  transform-origin: left center;
  transform: scaleX(0);
}

/* SplitText line masks — keeps glyph descenders from clipping. */
.motion .split-line {
  padding-bottom: .08em;
  margin-bottom: -.08em;
}


/* == 30 Colour themes and showcase ========================================
   The accent family from tokens.css, wired up. A .t-* class sets --accent
   and its tints on a container; everything below paints from those, so one
   class recolours a whole card, row or stat without touching its rules.
   ========================================================================= */

.t-blue {
  --accent: var(--a-blue);
  --accent-soft: var(--a-blue-soft);
  --accent-soft-2: var(--a-blue-soft-2);
  --accent-chip: var(--a-blue-chip);
  --accent-lt: var(--a-blue-lt);
}

.t-green {
  --accent: var(--a-green);
  --accent-soft: var(--a-green-soft);
  --accent-soft-2: var(--a-green-soft-2);
  --accent-chip: var(--a-green-chip);
  --accent-lt: var(--a-green-lt);
}

.t-ember {
  --accent: var(--a-ember);
  --accent-soft: var(--a-ember-soft);
  --accent-soft-2: var(--a-ember-soft-2);
  --accent-chip: var(--a-ember-chip);
  --accent-lt: var(--a-ember-lt);
}

.t-violet {
  --accent: var(--a-violet);
  --accent-soft: var(--a-violet-soft);
  --accent-soft-2: var(--a-violet-soft-2);
  --accent-chip: var(--a-violet-chip);
  --accent-lt: var(--a-violet-lt);
}

.t-gold {
  --accent: var(--a-gold);
  --accent-soft: var(--a-gold-soft);
  --accent-soft-2: var(--a-gold-soft-2);
  --accent-chip: var(--a-gold-chip);
  --accent-lt: var(--a-gold-lt);
}

/* Inside an ink section the light sibling is the readable one. */
.on-ink .t-blue,
.on-ink .t-green,
.on-ink .t-ember,
.on-ink .t-violet,
.on-ink .t-gold,
.section--ink .t-blue,
.section--ink .t-green,
.section--ink .t-ember,
.section--ink .t-violet,
.section--ink .t-gold,
.cta-band .t-blue,
.cta-band .t-green,
.cta-band .t-ember,
.cta-band .t-violet,
.cta-band .t-gold {
  --accent: var(--accent-lt);
}

/* --- Accent pill --------------------------------------------------------- */

/* The chip tint, not the soft fill: accent text at 13px needs the weaker
   background to clear 4.5:1. The border carries the colour instead. */
.pill--accent {
  border-color: var(--accent-soft-2);
  background-color: var(--accent-chip);
  color: var(--accent);
}

/* --- Swatch: a small colour dot that labels a themed block --------------- */

.swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: var(--r-pill);
  background-color: var(--accent);
  flex: none;
}

/* --- Showcase: full-bleed band of real work -----------------------------
   Screenshots of the concept sites at a size where the design is actually
   legible. The band scrolls horizontally on narrow screens and lays out as
   an offset grid from the medium breakpoint up.                          */

.showcase {
  --showcase-gap: var(--space-3);
  position: relative;
  margin-top: var(--space-5);
}

.showcase__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: min(78vw, 460px);
  gap: var(--showcase-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Keeps the track's own overflow from widening the page. */
  contain: paint;
  padding-block: 4px var(--space-2);
  padding-inline: var(--gutter);
  margin-inline: calc(var(--gutter) * -1);
  scrollbar-width: none;
}

.showcase__track::-webkit-scrollbar {
  display: none;
}

.showcase__item {
  scroll-snap-align: start;
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--r-lg);
  overflow: hidden;
  background-color: var(--accent-soft);
  border: 1px solid var(--rule);
  transition:
    border-color var(--dur-slow) var(--ease),
    transform var(--dur-slow) var(--ease);
}

.showcase__item:hover,
.showcase__item:focus-within {
  border-color: var(--accent);
  transform: translateY(-3px);
}

/* The lift is decoration; the tile must not move for anyone who asked it
   not to. Matches .pf-card. */
@media (prefers-reduced-motion: reduce) {
  .showcase__item,
  .showcase__item:hover,
  .showcase__item:focus-within {
    transform: none;
    transition: none;
  }
}

/* The whole tile opens the demo. The anchor fills the tile so every point
   on it — including the letterboxed background beside a contained image —
   is clickable, and the figcaption alone still names the destination for a
   screen reader (see aria-labelledby in the markup). */
.showcase__link {
  position: absolute;
  inset: 0;
  display: block;
  cursor: pointer;
}

/* The default ring sits outside the border box, which .showcase__item's
   overflow: hidden would clip away entirely. Pull it back inside so a
   keyboard focus is still visible. */
.showcase__link:focus-visible {
  outline-offset: calc(var(--focus-offset) * -1);
}

/* object-fit: contain rather than cover — cropping loses the outer edge of
   every screenshot, and the one shot with a taller aspect ratio also lost
   content vertically. Letterboxing against the tile's own accent tint reads
   better than cutting a page off. */
.showcase__img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* Top-aligned so a shorter screenshot's header sits flush with the tile's
     top edge instead of centring it with even letterbox space above. */
  object-position: top center;
  transition: filter var(--dur-slower) var(--ease);
}

.showcase__item:hover .showcase__img {
  filter: saturate(1.06);
}

.showcase__cap {
  position: absolute;
  z-index: var(--z-raised);
  /* The caption and the link are both positioned at the same layer, and the
     caption comes later in the DOM, so it would otherwise swallow clicks on
     the bottom-left of the tile. It is decorative text — let them through. */
  pointer-events: none;
  inset-inline: var(--space-2);
  bottom: var(--space-2);
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: 8px 14px;
  border-radius: var(--r-pill);
  background-color: var(--card);
  border: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--fg);
  width: max-content;
  max-width: calc(100% - var(--space-4));
}

/* Narrow screens let the caption wrap onto a second line rather than
   truncating a demo's name. From 901px there is room for one line. */
.showcase__cap span {
  overflow: hidden;
}

@media (min-width: 901px) {
  .showcase__track {
    grid-auto-flow: initial;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    grid-auto-columns: initial;
    /* The tiles carry their own 16:10 ratio now, so the row sizes to them. */
    grid-auto-rows: auto;
    overflow-x: visible;
    contain: none;
    padding-inline: 0;
    margin-inline: 0;
    gap: var(--space-4);
  }

  /* Two up, two down, every tile the same page-shaped 16:10. The earlier
     4-2 / 2-4 rhythm looked better as a diagram than in practice: these are
     screenshots of whole pages, and squeezing one into a short wide tile
     crops it to an arbitrary horizontal band with no beginning or end. Equal
     page-shaped tiles let each screenshot read as a page.                */
  .showcase__item {
    grid-column: span 3;
  }


  .showcase__cap span {
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* --- Thumb strip: a row of phone screenshots ----------------------------
   Small, equal-sized portrait shots. Scrolls on narrow screens and fits the
   row from the medium breakpoint up. Used where a section needs to show the
   range of the work without giving up the space a full band would take.
   ------------------------------------------------------------------------ */

.thumbstrip {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 132px;
  gap: var(--space-3);
  overflow-x: auto;
  contain: paint;
  padding-block: 4px;
  padding-inline: var(--gutter);
  margin-inline: calc(var(--gutter) * -1);
  scrollbar-width: none;
}

.thumbstrip::-webkit-scrollbar {
  display: none;
}

.thumbstrip__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  text-decoration: none;
  color: var(--fg);
}

.thumbstrip__shot {
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--rule-2);
  background-color: var(--surface-2);
  transition:
    border-color var(--dur-slow) var(--ease),
    transform var(--dur-slow) var(--ease);
}

.thumbstrip__item:hover .thumbstrip__shot,
.thumbstrip__item:focus-visible .thumbstrip__shot {
  border-color: var(--accent);
  transform: translateY(-4px);
}

/* 2:3 rather than the phone's own 1:2 — the strip stays a strip instead of
   becoming a wall of tall slivers. The shots crop from the top. */
.thumbstrip__shot img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 2 / 3;
  object-fit: cover;
  object-position: top center;
}

.thumbstrip__label {
  display: flex;
  align-items: center;
  gap: .5em;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-mono);
  color: var(--fg-2);
  line-height: 1.4;
}

@media (min-width: 901px) {
  .thumbstrip {
    grid-auto-flow: initial;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    grid-auto-columns: initial;
    overflow-x: visible;
    contain: none;
    padding-inline: 0;
    margin-inline: 0;
    gap: var(--space-4);
  }
}


/* --- Device pair: a desktop shot with its phone alongside ---------------- */

.device-pair {
  position: relative;
  padding-bottom: var(--space-6);
}

.device-pair__phone {
  position: absolute;
  right: var(--space-3);
  bottom: 0;
  width: 26%;
  max-width: 132px;
  border-radius: var(--r-md);
  border: 1px solid var(--rule-2);
  background-color: var(--card);
  overflow: hidden;
  box-shadow: var(--shadow-hairline);
}

.device-pair__phone img {
  display: block;
  width: 100%;
  height: auto;
}


/* == 29 Print ============================================================= */

@media print {
  .nav {
    position: static;
    border-bottom: 1px solid var(--line);
  }

  .nav__actions,
  .nav__overlay,
  .skip-link,
  .scroll-progress,
  .to-top,
  .cursor-badge,
  .copy-chip,
  .pf-nav,
  .cta-band__contact,
  .deco-bracket,
  .deco-mark {
    display: none;
  }

  .section--ink,
  .cta-band,
  .footer,
  .card--ink {
    --surface: var(--paper);
    --surface-2: var(--paper-2);
    --surface-card: var(--card);
    --fg: var(--text);
    --fg-2: var(--text-2);
    --rule: var(--line);
    --rule-2: var(--line-2);
    --mark: var(--text);
    background-color: transparent;
    color: var(--text);
  }

  .section,
  .cta-band,
  .footer,
  .hero {
    padding-block: var(--space-5);
  }

  .pf-track {
    display: block;
    overflow: visible;
  }

  .pf-card {
    break-inside: avoid;
    margin-bottom: var(--space-3);
  }

  .accordion__panel {
    grid-template-rows: 1fr;
  }

  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
  }
}
