/* ==========================================================================
   Health Promotion Specialist Portfolio
   Single stylesheet. No web fonts, no third-party requests: the type is
   the reader's own, which is the honest choice for a site about readability.
   Theme follows the operating system; there is no toggle and no flash.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  color-scheme: light;

  /* Type. The mockup sets its display text in a geometric sans, so the
     hierarchy comes from weight, size and tracking rather than from a second
     family. Everything resolves to faces the reader already has installed:
     there is no web font, and therefore no third-party request. */
  --ff-display: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, "Noto Sans", sans-serif;
  --ff-body: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    Arial, "Noto Sans", sans-serif;
  --ff-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;
  /* The handwritten flourish in the hero. Every face here ships with an
     operating system; the last entry is the generic fallback, so a machine with
     none of them still renders something readable rather than nothing. */
  --ff-script: "Snell Roundhand", "Apple Chancery", "Segoe Script",
    "Brush Script MT", "URW Chancery L", cursive;
  /* The pull quote is the one place the mockup leaves the sans behind. Again,
     system faces only: a webfont here would be a third-party request. */
  --ff-serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia,
    "Times New Roman", serif;

  --fs-xs: 0.75rem;
  --fs-sm: 0.8125rem;
  --fs-base: 1rem;
  --fs-md: 1.0625rem;
  --fs-lg: 1.25rem;
  --fs-xl: 1.5rem;
  --fs-2xl: 1.875rem;
  /* Measured off the mockup: its headline sets three lines in a column the same
     width as ours, which works out to about 43px per line. At 3.4rem ours set
     four lines and pushed the whole page a third too tall. */
  --fs-3xl: clamp(1.9rem, 1.05rem + 2.3vw, 2.75rem);

  --lh-tight: 1.12;
  --lh-snug: 1.3;
  --lh-body: 1.65;

  --tracking-label: 0.08em;
  --tracking-display: -0.018em;

  /* Space */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4.5rem;

  --radius-sm: 8px;
  --radius: 14px;
  --radius-lg: 20px;
  --radius-pill: 999px;

  --measure: 40rem;      /* reading column */
  --shell: 74rem;        /* outer shell */
  /* Measured off the mockup, whose content runs to 94% of the page width. At
     80rem our hero columns were each narrower than the mockup's, which pushed
     the headline onto a fourth line and the value pills onto two rows. */
  --shell-wide: 84rem;   /* sample wall */

  --border-w: 1px;
  --focus-w: 3px;
}

/* --- Light ---------------------------------------------------------------- */
/* Every value below was measured off the client's mockup rather than eyeballed:
   the accent is a teal (#359496, sampled from the solid CTA fill), the ink is a
   dark navy-teal (#123a56, sampled from the headline), and the page sits on
   near-white (#fefefe) rather than a tinted grey. */
:root {
  --bg: #fefefe;
  --bg-elev: #ffffff;
  --bg-sunken: #f4f8fb;
  --bg-inset: #f6fafd;

  --text: #123a56;
  --text-muted: #5a6a72;
  --text-faint: #8697a0;

  --line: #e6ecf1;
  --line-strong: #d3dde5;

  --accent: #2c7f7a;
  --accent-hover: #22645f;
  --accent-ink: #ffffff;
  --accent-soft: #e9f4f8;
  --accent-ring: rgba(53, 148, 150, 0.32);

  /* The teal the mockup uses for the highlighted phrase in the headline, the
     small uppercase section labels, the section dashes and every button. */
  --brand: #359496;
  --brand-ink: #ffffff;
  --brand-soft: #e9f4f8;

  /* The mockup carries TWO mint levels, not one, and the hero blob is the
     second. Sampled from the blob itself: the most common tone in its colour
     family is #c6e4e4, against #e9f4f8 for the credibility band.

     The blob used to borrow --brand-soft, which is the band's pale mint. On a
     #fefefe page that is a difference of about 5% luminance, so the shape was
     effectively invisible and the hero read as white. The blob was not the
     whole of that: the wash below was the larger half. White share is measured
     by tools/palette.py, which states its threshold, because an earlier draft
     of this comment quoted a figure from a stricter one and the two are not
     comparable. */
  --brand-mid: #c6e4e4;

  /* The small circular icon chip, and the mockup uses ONE colour for it
     everywhere: sampled at the hero trust badges (#d3eded), the credibility
     band (#d2eaee) and the expertise tiles (#d5eef1), which agree to within
     three levels per channel. It sits between --brand-soft and --brand-mid.

     Two things were wrong with this before. The chips that existed borrowed
     --brand-soft, which is the same value as the credibility band, so a chip
     sitting on that band was invisible. And the credibility band's chip
     borrowed --band-line, a translucent ink meant for hairlines, which over
     the band resolved to #cbd9e1: a blue-grey where the mockup has mint. A
     divider colour is not a fill colour, even when the alpha looks harmless. */
  --chip: #d3ecef;

  /* A TINTED band, not a dark one. The mockup sets the credibility figures and
     the contact band on pale mint with the page's own dark ink, so these tokens
     exist to keep that surface consistent, NOT to invert the text. Naming it
     --band-ink previously invited exactly the mistake that put near-white labels
     on white cards, so the surface class is now .band--tint. */
  --band: #e9f4f8;
  --band-ink: #123a56;
  --band-muted: #5a6a72;
  --band-line: rgba(18, 58, 86, 0.14);

  --teal: #2c7f7a;
  --teal-soft: #e9f4f8;

  --amber: #8a5a00;
  --amber-soft: #fbf0da;

  --rose: #a32c52;
  --rose-soft: #fbe7ed;

  --shadow-sm: 0 1px 2px rgba(18, 58, 86, 0.05);
  --shadow: 0 2px 4px rgba(18, 58, 86, 0.04), 0 10px 24px rgba(18, 58, 86, 0.07);
  --shadow-lg: 0 18px 48px rgba(18, 58, 86, 0.14);

  --header-bg: rgba(255, 255, 255, 0.92);
  --scrim: rgba(18, 58, 86, 0.62);
  --mount: #ffffff;

  /* One hue per programme area. The filter chips and the card rules read
     from the same set, so the colour means the same thing everywhere. */
  --area-germas: #2c7f7a;
  --area-phbs: #35948f;
  --area-stbm: #4a8f6a;
  --area-stunting: #b06a3a;
  --area-gizi: #9a7b2e;
  --area-imunisasi: #8a5a86;
  --area-p2m: #a8443f;
  --area-napza: #5f6ea8;
  --area-lansia: #9c5570;
  --area-literasi-digital: #3f7fa0;
  --area-kehumasan: #6b7280;
}

/* --- One theme, and it is light ------------------------------------------- */
/* There is no dark theme here, and that is a decision rather than an omission.
   The mockup specifies a light palette and nothing else, and the client asked
   for that palette to be followed, not for a second one to be invented from it.

   An earlier revision did derive one, and the derivation is what produced two
   of the bugs recorded above: a hero blob that collapsed into the band because
   the dark ramp reused --brand-soft, and a chip that lost its edge against
   every surface it could land on. One palette means one set of contrast
   relationships to get right, so the second one is gone rather than fixed.

   The consequence for anything that reads the operating system: the site
   ignores it. `color-scheme: light` on :root above is what stops the browser
   from darkening form controls and scrollbars to match the device, which would
   otherwise leave a light page carrying dark UI furniture. */



/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 6rem;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--ff-body);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 600;
  line-height: var(--lh-snug);
  text-wrap: balance;
}

h1 {
  font-family: var(--ff-display);
  font-size: var(--fs-3xl);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-display);
  font-weight: 600;
}

h2 {
  font-size: var(--fs-xl);
  letter-spacing: -0.01em;
}

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

p {
  margin: 0 0 var(--sp-4);
  text-wrap: pretty;
}

p:last-child {
  margin-bottom: 0;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

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

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}

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

abbr[title] {
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
  cursor: help;
}

:focus-visible {
  outline: var(--focus-w) solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

::selection {
  background: var(--accent-soft);
  color: var(--text);
}

/* --------------------------------------------------------------------------
   3. Shell and layout
   -------------------------------------------------------------------------- */

.main {
  display: block;
  max-width: var(--shell);
  margin: 0 auto;
  padding: var(--sp-7) var(--sp-5) var(--sp-8);
}

/* The home page is a run of full-bleed bands, each carrying its own inner
   container. The shared reading column that wraps every other page has to step
   aside, or the credibility band would stop short of the viewport edges and the
   page would read as a document rather than as a landing page. */
.page--home .main {
  max-width: none;
  padding: 0;
}

.section {
  margin-top: var(--sp-8);
  padding-top: var(--sp-6);
  border-top: var(--border-w) solid var(--line);
}

.section__title {
  font-family: var(--ff-body);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--sp-5);
}

.section__more {
  margin-top: var(--sp-5);
  font-size: var(--fs-sm);
}

.page-head {
  max-width: var(--measure);
  margin-bottom: var(--sp-7);
}

.page-head__lead {
  margin-top: var(--sp-4);
  font-size: var(--fs-lg);
  line-height: 1.5;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   4. Skip link
   -------------------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -4rem;
  z-index: 200;
  padding: var(--sp-2) var(--sp-4);
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  transition: top 120ms ease-out;
}

.skip-link:focus {
  top: var(--sp-4);
  color: var(--accent-ink);
}

/* --------------------------------------------------------------------------
   5. Demo notice
   -------------------------------------------------------------------------- */

.demo-notice {
  margin: 0;
  padding: var(--sp-2) var(--sp-5);
  background: var(--amber-soft);
  color: var(--amber);
  border-bottom: var(--border-w) solid var(--line);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-align: center;
}

/* --------------------------------------------------------------------------
   6. Header
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--header-bg);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: var(--border-w) solid var(--line);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  max-width: var(--shell-wide);
  margin: 0 auto;
  padding: var(--sp-3) var(--sp-5);
}

/* --- brand ---------------------------------------------------------------- */

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-right: auto;
  color: var(--text);
  text-decoration: none;
  line-height: 1.15;
}

.brand__mark {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: var(--brand-soft);
  color: var(--accent);
}

.brand__icon {
  width: 1.25rem;
  height: 1.25rem;
}

.brand__text {
  display: flex;
  flex-direction: column;
}

.brand__role {
  font-family: var(--ff-display);
  font-size: var(--fs-base);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.brand__suffix {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--brand);
}

.brand:hover .brand__role {
  color: var(--accent);
}

/* --- navigation ----------------------------------------------------------- */

.site-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
}

.site-nav__list {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-nav__link {
  display: inline-block;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  transition: background-color 120ms ease-out, color 120ms ease-out;
}

.site-nav__link:hover {
  background: var(--bg-sunken);
  color: var(--text);
}

.site-nav__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

/* --- the drawer toggle ---------------------------------------------------- */

.nav-toggle {
  display: none;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border: var(--border-w) solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--bg-elev);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}

.nav-toggle__icon {
  width: 1.15rem;
  height: 1.15rem;
}

/* One button, two states. Which label and which glyph show is driven entirely
   by aria-expanded, which the script keeps in step with the panel's real
   state, so the button can never say "Menu" over an open drawer. */
.nav-toggle__label--close,
.nav-toggle__icon--close,
.nav-toggle[aria-expanded="true"] .nav-toggle__label--open,
.nav-toggle[aria-expanded="true"] .nav-toggle__icon--open {
  display: none;
}

.nav-toggle[aria-expanded="true"] .nav-toggle__label--close {
  display: inline;
}

.nav-toggle[aria-expanded="true"] .nav-toggle__icon--close {
  display: block;
}

/* --- language switch ------------------------------------------------------ */

.lang-switch {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  padding: var(--sp-1) var(--sp-2);
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
}

.lang-switch__item {
  padding: 0 var(--sp-1);
  color: var(--text-faint);
  text-decoration: none;
}

a.lang-switch__item:hover {
  color: var(--accent);
}

.lang-switch__item[aria-current="true"] {
  color: var(--accent);
}

.lang-switch__sep {
  color: var(--line-strong);
}

/* --------------------------------------------------------------------------
   7. Buttons, labels and shared pieces
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-5);
  border: var(--border-w) solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--bg-elev);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: transform 120ms ease-out, border-color 120ms ease-out,
    background-color 120ms ease-out, box-shadow 120ms ease-out;
}

.btn:hover {
  border-color: var(--accent);
  color: var(--accent);
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.btn--primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--accent-ink);
}

/* The header call to action. It sits on the brighter brand green rather than
   the deep accent, so it reads as an invitation instead of competing with the
   page's own primary action. */
.btn--brand {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  font-size: var(--fs-xs);
  white-space: nowrap;
}

.btn--brand:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.btn--ghost {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--text);
}

.btn__icon {
  flex: 0 0 auto;
  width: 1.05em;
  height: 1.05em;
}

.linkish {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 0.18em;
  cursor: pointer;
}

/* --- shared pieces -------------------------------------------------------- */

.icon {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
}

/* The small uppercase line that opens a section. */
.eyebrow {
  margin: 0 0 var(--sp-4);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brand);
}

/* A short label in both languages: the page's language first, the other one
   underneath in a smaller, quieter face. One element so it stays a single
   flex or grid item wherever it is used. */
.pair {
  display: block;
}

.pair__primary {
  display: block;
}

.pair__secondary {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: 1.35;
  color: var(--text-muted);
}

h1 .pair__secondary,
h2 .pair__secondary,
h3 .pair__secondary {
  font-size: var(--fs-sm);
}

/* --------------------------------------------------------------------------
   8. Hero
   -------------------------------------------------------------------------- */

/* The wash is a background on the section itself rather than a pseudo-element
   with a negative inset. An earlier version widened a pseudo-element past its
   parent and pushed the whole page into horizontal overflow, 8px at 390px
   wide, 104px at 768px. A background cannot overflow its own box, so the
   failure mode is designed out rather than tuned away.

   The wash is the mockup's own hero tint (#f6fafd, --bg-inset), which is
   lighter than the credibility band's mint. Using --brand-soft here made the
   hero and the band the same value, and the two read as one block.

   The gradient ends on an OPAQUE colour, not on transparent. It used to fade
   to rgba(0,0,0,0) at 92%, which looks like a wash but is really the tint
   draining out: sampled down the left margin, the live hero ran #f6fafd at the
   top to white by the badges, and the whole lower half of a 537px band was
   white. The mockup does the opposite, deepening #f6fafd to #f4f9fc and running
   straight into the credibility band with no white gap. Fading to --bg-sunken
   holds the tint to the band edge, which is the mockup's shape.

   Chrome interpolates gradients in premultiplied alpha, so the old form kept
   the hue and dropped only the alpha. That is why the mistake was invisible in
   the source and obvious in a screenshot.

   Measured with tools/palette.py over the hero plus the credibility band, the
   white share went from 48.9% to 13.2%, against the mockup's 15.4%. */
.hero {
  background: linear-gradient(180deg, var(--bg-inset) 0%, var(--bg-sunken) 100%);
  padding: var(--sp-5) 0 var(--sp-5);
}

.hero__inner {
  display: grid;
  /* Proportions taken from the mockup: copy 37% of the width, photograph 31%,
     the about card 23%. The copy column is the widest of the three because it
     carries the headline at display size, and a headline that wraps to six
     lines stops reading as a headline. */
  grid-template-columns: minmax(0, 1.19fr) minmax(0, 1fr) minmax(0, 17rem);
  gap: var(--sp-6);
  align-items: start;
  max-width: var(--shell-wide);
  margin: 0 auto;
  padding: 0 var(--sp-5);
}

/* Her name, at display size. It is the only element on the site that says who
   the page is about, so it is set as the headline rather than as a label. */
.hero__title {
  margin: 0;
  font-size: var(--fs-3xl);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-display);
  text-wrap: balance;
}

/* The proposition: the one sentence a hiring manager must read before
   scrolling. It carries the "what she does" load now that the headline is her
   name, so it is larger than the supporting line and in full text colour rather
   than muted. */
.hero__proposition {
  margin: var(--sp-4) 0 0;
  max-width: 32ch;
  font-size: var(--fs-lg);
  line-height: 1.4;
}

/* The supporting line: who she works for. Quieter than the proposition, because
   it answers the second question a reader asks rather than the first. */
.hero__supporting {
  margin: var(--sp-3) 0 0;
  max-width: 38ch;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-muted);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin: var(--sp-6) 0 0;
}

/* auto-fit rather than a fixed count: the pills are two lines each, so how
   many fit on a row depends on the language. A 12rem floor lands on a tidy
   two-by-two in both languages; a narrower floor would give three and then
   orphan the fourth on a line of its own. */
/* Four in one row, as in the mockup. At a 12rem floor only three fitted the
   copy column and the fourth wrapped alone onto a second row, which read as a
   mistake rather than as a set. */
.values {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: var(--sp-3);
  margin: var(--sp-5) 0 0;
  padding: var(--sp-4) 0 0;
  border-top: var(--border-w) solid var(--line);
  list-style: none;
}

.value-pill {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: 700;
}

/* The mockup gives every small icon the same mint chip, and the hero value
   pills are no exception: bare, a 1rem teal tick on near-white was the one
   place the chip was simply missing.

   content-box keeps the glyph at its own size while the padding grows the
   circle around it, the same construction as .tile__icon. The padding is
   --sp-1 rather than --sp-2, and the pill's own gap is --sp-1, because this
   column is only 121px wide at 1440: a 2rem chip plus an 8px gap takes 40px of
   it, which cost the badge row four extra text lines (10 before, 14 after) and
   left the four badges visibly ragged. At 1.5rem plus a 4px gap the row fits
   the way the mockup draws it.

   The glyph moves from --brand to --accent for a measurable reason: --brand on
   --chip is 2.9:1, under the 3:1 floor for a graphical object, where --accent
   is 3.9:1. It is also what every other chip on the page already uses. */
.value-pill__icon {
  box-sizing: content-box;
  flex: 0 0 auto;
  width: 1rem;
  height: 1rem;
  padding: var(--sp-1);
  border-radius: 50%;
  background: var(--chip);
  color: var(--accent);
}

.value-pill .pair__secondary {
  font-size: var(--fs-xs);
  font-weight: 500;
}

/* --- the portrait, the blob behind it, and the two overlapping cards ------- */

/* The mockup sets the portrait on a soft organic blob rather than inside a
   bordered card. The blob is pinned with `inset: 0` and NOT with negative
   insets: an absolutely positioned overlay with a negative horizontal inset
   overflows its container, which is the exact bug the hero wash was rewritten
   to avoid. Filling the box is safe, because a box cannot overflow itself.

   The blob is made larger than the portrait by PADDING this box, not by
   insetting the blob negatively. The portrait sits in the content box; the blob
   fills the padding box. So the shape shows around the photograph with no
   negative offsets anywhere, and the overflow failure mode stays designed out.

   That padding is load-bearing. Without it the blob and the portrait are the
   same box, the blob is hidden behind the image, and the hero reads as white:
   measured against the mockup it was 87% white where the mockup's hero is 66%.
   The proportions here are the mockup's, where the photograph occupies roughly
   two thirds of the blob's width.

   The padding is asymmetric on purpose, and the asymmetry arrived with the real
   photograph. While the slot held generated art, the artwork was a rounded frame
   drawn inside a transparent canvas, so the image's own margin supplied the
   space on both sides and even padding looked right. A photograph has no such
   margin: it is the content box, edge to edge. With 17% on each side the
   handwritten flourish landed on the client's face, because it is positioned
   from the right edge of this box.

   The mockup puts that flourish on the blob, to the right of the photograph,
   and its blob runs about 96px past the photograph at the comp's scale, roughly
   a quarter of the frame's width. So the room comes off the left and goes to the
   right: 24% there against 10% here, which holds the photograph at 66% of the
   blob, the same share as before, and the same share as the comp.

   `--band` names that right-hand gutter because two rules depend on it: this
   padding, and the width of the flourish that sits in it. Sized independently
   the flourish was correct at 1440 and climbed back onto the photograph at
   390, where a fixed 6rem cap outgrew a gutter that had shrunk to 74px.

   The padding lives on `.hero__frame` rather than here, and that indirection is
   load-bearing. A percentage padding resolves against the CONTAINING BLOCK, not
   against the element's own width, and `max-width` does not change the
   containing block. So with the padding on this element the percentages were
   computed from the grid area while the box itself was capped at 28rem, and the
   two only agreed while the grid area happened to be about 28rem wide.

   Between roughly 544px and 1088px the grid collapses to a single column, so the
   grid area is the full 720 to 976px while this box stays at 448px, and the
   padding was being charged on the grid area: 24% + 10% of 976px is 332px of
   padding inside a 448px box, which left a 116px photograph. The bug predates
   the photograph, the placeholder art shrank with it, and neither the sweep nor
   a screenshot could see it, because nothing overflowed and nothing lost
   contrast. tools/boxes.mjs measures the share, which is how it surfaced.

   `.hero__frame` is a plain block inside this one, so its containing block is
   this element's content box: 448px, the capped width, whatever the grid area
   happens to be. Its padding box is the same 448px as this element's, which is
   why the blob below still lines up exactly with it. */
.hero__media {
  --band: 24%;
  position: relative;
  width: 100%;
  max-width: 28rem;
  justify-self: center;
}

.hero__frame {
  padding: 8% var(--band) 6% 10%;
}

.hero__blob {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: 48% 52% 46% 54% / 52% 46% 54% 48%;
  background: var(--brand-mid);
}

/* The portrait is the content box; the blob is the padding box behind it.

   It carries its own corner radius. The placeholder that used to sit here drew
   its own rounded frame inside a transparent canvas, so the rounding arrived
   baked into the file. A photograph does not, and without this the picture
   would be a hard rectangle sitting on the blob, which is not the shape the
   mockup draws. 1.5rem on a photograph that renders about 296px wide is a
   little over 8% of the width, which is what the comp's frame measures. */
.hero__portrait {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
  border-radius: 1.5rem;
}

/* The handwritten flourish, top right. System cursive faces only: a web font
   would be a third-party request, and this site makes none.

   It is sized to the right-hand band rather than to the box, so it sits on the
   blob beside the photograph instead of across it. Reading the width from
   --band is what keeps that true at every width: the gutter is 105px at 1440
   and 74px at 390, and the flourish follows it down instead of staying wide.
   `top: 25%` puts it about a fifth of the way down the photograph, which is
   where the mockup sets it.

   It used to be 1.3rem over 9rem and it fitted, because the image underneath
   was placeholder art with a transparent margin. That margin is gone. */
.hero__script {
  position: absolute;
  z-index: 2;
  top: 25%;
  right: var(--sp-2);
  max-width: calc(var(--band) - var(--sp-2) * 2);
  margin: 0;
  font-family: var(--ff-script);
  font-size: 1.2rem;
  font-weight: 500;
  line-height: 1.15;
  text-align: right;
  color: var(--accent);
  transform: rotate(-5deg);
}

/* Both overlays stay inside the media box. Overhanging them would reintroduce
   exactly the overflow the blob above was written to avoid. */
.hero__quote {
  position: absolute;
  z-index: 2;
  right: 0;
  bottom: 5%;
  max-width: 12rem;
  margin: 0;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius);
  border-left: 3px solid var(--brand);
  background: var(--bg-elev);
  color: var(--text);
  font-size: var(--fs-sm);
  font-style: italic;
  line-height: 1.45;
  box-shadow: var(--shadow);
}

/* --- the "about me" card -------------------------------------------------- */

.hero__aside {
  padding: var(--sp-5);
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-elev);
  box-shadow: var(--shadow-sm);
}

.hero__aside-title {
  margin: 0;
  font-size: var(--fs-lg);
  line-height: 1.25;
  letter-spacing: -0.01em;
}

.hero__aside-body {
  margin: var(--sp-3) 0 0;
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--text-muted);
}

.aside-list {
  display: grid;
  gap: var(--sp-2);
  margin: var(--sp-4) 0 0;
  padding: var(--sp-4) 0 0;
  border-top: var(--border-w) solid var(--line);
  list-style: none;
}

.aside-list__item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-sm);
  font-weight: 600;
}

/* The same chip as everywhere else, at the size the mockup draws these rows.
   The padding is a literal rather than --sp-2 because the glyph here is
   1.15rem and --sp-2 would make the circle 2.15rem, noticeably larger than
   the 1.9rem the mockup uses. */
.aside-list__icon {
  box-sizing: content-box;
  flex: 0 0 auto;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0.375rem;
  border-radius: 50%;
  background: var(--chip);
  color: var(--accent);
}

/* --------------------------------------------------------------------------
   10. Cards
   -------------------------------------------------------------------------- */

.cards {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 19rem), 1fr));
}

/* One mapping, declared once. Any element carrying data-area inherits the
   right hue, so the card rule, the area tag and the programme-area list can
   never disagree about what colour Germas is. */
[data-area="germas"] { --area-tone: var(--area-germas); }
[data-area="phbs"] { --area-tone: var(--area-phbs); }
[data-area="stbm"] { --area-tone: var(--area-stbm); }
[data-area="stunting"] { --area-tone: var(--area-stunting); }
[data-area="gizi"] { --area-tone: var(--area-gizi); }
[data-area="imunisasi"] { --area-tone: var(--area-imunisasi); }
[data-area="p2m"] { --area-tone: var(--area-p2m); }
[data-area="napza"] { --area-tone: var(--area-napza); }
[data-area="lansia"] { --area-tone: var(--area-lansia); }
[data-area="literasi-digital"] { --area-tone: var(--area-literasi-digital); }
[data-area="kehumasan"] { --area-tone: var(--area-kehumasan); }

.card {
  display: flex;
  position: relative;
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-elev);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  transition: transform 160ms ease-out, box-shadow 160ms ease-out,
    border-color 160ms ease-out;
}

.card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--area-tone, var(--line-strong));
}

.card:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow);
  border-color: var(--line-strong);
}

.card.is-hidden {
  display: none;
}

.card__link {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-5);
  color: inherit;
  text-decoration: none;
}

.card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.card__title {
  font-family: var(--ff-display);
  font-size: var(--fs-xl);
  font-weight: 600;
  letter-spacing: -0.012em;
  line-height: 1.25;
}

.card__summary {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-base);
  line-height: 1.55;
}

.card__meta {
  margin-top: auto;
  padding-top: var(--sp-3);
  border-top: var(--border-w) solid var(--line);
  font-family: var(--ff-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.card:hover .card__title {
  color: var(--accent);
}

/* --------------------------------------------------------------------------
   11. Tags

   No badge rule lives here any more. A per-work publication-status badge was
   removed from the at-a-glance table: every work carried the same value, so it
   told a reader nothing. The `clearance` field is still required in every case
   study and still gates tools/deploy.py; it is simply not rendered.
   -------------------------------------------------------------------------- */

.tag {
  display: inline-block;
  padding: 0.2rem var(--sp-3);
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.tag--quiet {
  background: transparent;
  border: var(--border-w) solid var(--line);
  color: var(--text-muted);
}

/* The programme-area tag takes its hue from the card it sits in, so the same
   colour means the same programme everywhere: the card's top rule, this tag,
   and the row in the programme-area list below. */
.tag--area {
  background: var(--bg-inset);
  color: var(--area-tone, var(--accent));
}

/* --------------------------------------------------------------------------
   12. Credibility band
   -------------------------------------------------------------------------- */

/* A tinted surface is its own modifier, NOT the base .band class. The split
   bands further down also carry .band, so a rule keyed to .band painted their
   paired labels in the tinted surface's ink: a near-white at 74% alpha when
   the band was dark, and invisible against the page background. The base
   class stays neutral and each surface declares its own colours.

   The band is LIGHT, matching the mockup: pale mint with the page's own dark
   navy ink. The name is --tint rather than --ink because --ink described the
   old dark surface and would now be a lie. */
.band--tint {
  background: var(--band);
  color: var(--band-ink);
}

.band--tint .pair__secondary {
  color: var(--band-muted);
}

/* The mockup separates the figures from the pull quote with a hairline. It is
   a border on the quote so it disappears with it when the column stacks. */
.band--tint .pullquote {
  border-left: var(--border-w) solid var(--band-line);
}

.band__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 17rem);
  gap: var(--sp-6);
  align-items: center;
  max-width: var(--shell-wide);
  margin: 0 auto;
  padding: var(--sp-5) var(--sp-5);
}

.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.stat {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "icon value"
    "icon label";
  align-items: center;
  column-gap: var(--sp-3);
}

.stat__icon {
  grid-area: icon;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--chip);
  color: var(--accent);
}

.stat__glyph {
  width: 1.35rem;
  height: 1.35rem;
}

.stat__value {
  grid-area: value;
  font-size: var(--fs-2xl);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.stat__label {
  grid-area: label;
}

.stat__label .pair__primary {
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.3;
}

.stat__label .pair__secondary {
  font-size: var(--fs-xs);
}

/* The pull quote. The mockup sets it in an italic serif with a short accent
   rule beneath: the only serif on the page. The hairline on the left is the
   divider between the figures and the quote, and it comes from the band's own
   rule so it disappears with the column when the layout stacks. */
.pullquote {
  margin: 0;
  padding-left: var(--sp-5);
  font-family: var(--ff-serif);
  font-size: var(--fs-xl);
  font-style: italic;
  line-height: 1.35;
}

.pullquote::after {
  content: "";
  display: block;
  width: 2.5rem;
  height: 3px;
  margin-top: var(--sp-3);
  border-radius: 2px;
  background: var(--brand);
}

/* --------------------------------------------------------------------------
   13. Split bands: two columns of content side by side
   -------------------------------------------------------------------------- */

.band--split {
  background: var(--bg);
  color: var(--text);
  border-top: var(--border-w) solid var(--line);
}

.band__inner--split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
  gap: var(--sp-7);
  align-items: start;
  padding: var(--sp-6) var(--sp-5);
}

.col__head {
  margin-bottom: var(--sp-4);
}

.col__head--split {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
}

.col__title {
  margin: 0;
  font-size: var(--fs-xl);
  letter-spacing: -0.015em;
}

/* Every section heading in the mockup is followed by a short accent rule. It is
   a pseudo-element rather than markup, so it cannot drift out of step with the
   heading and it is invisible to assistive technology by construction. */
.col__title::after,
.partners__title::after {
  content: "";
  display: inline-block;
  width: 2.5rem;
  height: 3px;
  margin-left: var(--sp-4);
  vertical-align: 0.32em;
  border-radius: 2px;
  background: var(--brand);
}

.col__lede {
  margin: var(--sp-2) 0 0;
  max-width: 46ch;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.col__head-link {
  margin: 0;
  white-space: nowrap;
}

.col__more {
  margin: var(--sp-5) 0 0;
}

.col__more a,
.col__head-link a {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--accent);
  font-size: var(--fs-sm);
  font-weight: 700;
  text-decoration: none;
}

.col__more a:hover,
.col__head-link a:hover {
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

.col__more-icon {
  flex: 0 0 auto;
  width: 1rem;
  height: 1rem;
}

/* --------------------------------------------------------------------------
   14. Expertise tiles
   -------------------------------------------------------------------------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Centred, with the glyph in a circle on a soft panel and no border, as in the
   mockup. The tile used to be a bordered card with icon and label left-aligned,
   which read as a list of items rather than as a set of competencies. */
.tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-3);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  text-align: center;
}

/* content-box keeps the circle concentric: the padding grows the box outward
   instead of shrinking the glyph inside a fixed 1.6rem. */
.tile__icon {
  box-sizing: content-box;
  width: 1.3rem;
  height: 1.3rem;
  padding: var(--sp-3);
  border-radius: 50%;
  background: var(--chip);
  color: var(--accent);
}

.tile__title {
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1.3;
}

/* --------------------------------------------------------------------------
   15. Featured project cards
   -------------------------------------------------------------------------- */

.projects {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.project {
  display: flex;
}

.project__link {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-3) var(--sp-3) var(--sp-4);
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-elev);
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  transition: border-color 140ms ease-out, transform 140ms ease-out,
    box-shadow 140ms ease-out;
}

.project__link:hover {
  border-color: var(--brand);
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}

/* The programme-area colour used to run along the card's top edge. It is gone:
   the photograph now sits flush to that edge, so the rule was painted behind an
   opaque image and had been invisible since the card was rebuilt. The area
   colour lives in the tag instead, where it can still be seen, and it still
   comes from [data-area] on the <li>, so the card, its tag and the work index
   agree on what colour Germas is. */

/* The photograph breaks out to the card's edges, as it does in the mockup,
   where it runs flush to the top, left and right with the text inset below.
   The negative margins are safe here because .project__link sets
   overflow: hidden, so they cannot reach the page edge, the failure mode that
   bit the hero wash. No border-radius: the card clips the corners for us. */
.project__media {
  position: relative;   /* anchors the category tag to the image */
  display: block;
  margin: calc(var(--sp-3) * -1) calc(var(--sp-3) * -1) 0;
  overflow: hidden;
  background: var(--bg-inset);
}

.project__media img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 800 / 520;
  object-fit: cover;
}

/* The category tag sits ON the photograph as a light pill, which is what the
   mockup does. It used to be a sibling below the image, which added a whole row
   to the card and read as a separate label rather than as a tag on the image.
   It keeps the programme-area hue, so the colour still means something. */
.project__media .tag {
  position: absolute;
  left: var(--sp-3);
  bottom: var(--sp-3);
  background: rgba(255, 255, 255, 0.94);
  color: var(--area-tone, var(--accent));
  box-shadow: var(--shadow-sm);
}

.project__title {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: 700;
  line-height: 1.3;
}

/* The mockup shows a two-line summary under each card title. It is read from
   the case study itself, so the card cannot claim what the detail page does
   not say. */
.project__summary {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-muted);
}

/* margin-top:auto pushes the figures to the bottom edge, so the three cards in
   a row line up even when their titles wrap to different heights. */
.project__stats {
  display: flex;
  gap: var(--sp-5);
  margin-top: auto;
  padding-top: var(--sp-3);
  border-top: var(--border-w) solid var(--line);
}

.mini-stat {
  display: block;
  min-width: 0;
}

.mini-stat__value {
  display: block;
  font-size: var(--fs-lg);
  font-weight: 700;
  line-height: 1.15;
  color: var(--accent);
}

.mini-stat__label {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted);
}

.project__cta {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 700;
}

.project__cta-icon {
  flex: 0 0 auto;
  width: 1rem;
  height: 1rem;
}

/* --------------------------------------------------------------------------
   16. Experience timeline
   -------------------------------------------------------------------------- */

.timeline {
  margin: 0;
  padding: 0 0 0 var(--sp-5);
  border-left: 2px solid var(--line);
  list-style: none;
}

.timeline__item {
  position: relative;
  padding-bottom: var(--sp-4);
}

.timeline__item:last-child {
  padding-bottom: 0;
}

/* 12px dot centred on a 2px border that sits one padding-width to the left:
   -(24px + 1px) - 6px = -31px. */
.timeline__item::before {
  content: "";
  position: absolute;
  left: -31px;
  top: 0.4em;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--bg);
  border: 3px solid var(--accent);
}

.timeline__period {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--text-faint);
}

.timeline__role {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-base);
}

.timeline__org {
  margin: 2px 0 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent);
}

.timeline__body {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   17. Services and partners
   -------------------------------------------------------------------------- */

.services {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Icon beside the title, not above it. The mockup sets the glyph and the
   heading on one line with the prose beneath, which keeps a three-card row
   compact; stacking them added about 45px to every card. */
.service {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: var(--sp-3);
  row-gap: var(--sp-3);
  padding: var(--sp-4);
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-elev);
}

.service__icon {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: var(--chip);
  color: var(--accent);
}

.service__glyph {
  width: 1.25rem;
  height: 1.25rem;
}

.service__title {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 700;
  line-height: 1.3;
}

/* Spans both columns so it sits under the icon-and-title row. */
.service__body {
  grid-column: 1 / -1;
  margin: 0;
}

.service__body .pair__primary {
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-muted);
}

.service__body .pair__secondary {
  margin-top: var(--sp-2);
  font-size: var(--fs-xs);
  line-height: 1.5;
}

/* The partner strip spans the band rather than sitting inside the services
   column, as it does in the mockup. Inside a 669px column the five wordmarks
   wrapped onto two rows and the strip cost 193px of height for what is one line
   of names. It has no top padding of its own: the columns above already carry
   their own bottom padding, and doubling it opened a 56px hole in the band. */
.partners {
  max-width: var(--shell-wide);
  margin: 0 auto;
  padding: 0 var(--sp-5) var(--sp-5);
}

.partners__title {
  margin: 0 0 var(--sp-3);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

/* Four equal columns, so the logos and their names line up as a wall rather
   than a row of names spread edge to edge. The grid steps 4 -> 2 -> 1; the
   later steps live in the 46rem and 28rem blocks at the end of the file. */
.partners__list {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-4) var(--sp-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Genuine artwork now, not wordmarks. Each logo is trimmed to its own content
   by tools/prepare_logos.py, so they share a height and keep their own width:
   the four run from a 3.1:1 wordmark to a 0.9:1 emblem, and forcing a common
   width would either shrink the wide ones to nothing or squash the tall one.

   Both max-* are set instead of a fixed height, so a narrow column shrinks the
   logo rather than letting it overflow.

   The name stays visible underneath. The band claims who commissioned the work,
   and one of these marks is a bare emblem with no lettering at all, so the name
   is doing work the logo cannot. */
.partner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  text-align: center;
  /* Two of the four names are too long for a quarter-width column and wrap.
     Balancing them stops the last word landing alone on its own line, which is
     how "Jakarta III" first rendered. Inherited by the span below. */
  text-wrap: balance;
  font-size: var(--fs-base);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text-faint);
}

.partner__logo {
  max-width: 100%;
  max-height: 44px;
  width: auto;
  height: auto;
}

/* The disclaimer is required in demo mode, but it does not need three lines:
   at 52ch it wrapped three times inside a 1280px shell that had room for one. */
.partners__note {
  max-width: 88ch;
  margin: var(--sp-3) 0 0;
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   18. Closing contact band
   -------------------------------------------------------------------------- */

.contact-band {
  background: var(--bg-sunken);
  border-top: var(--border-w) solid var(--line);
}

.contact-band__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr) auto;
  gap: var(--sp-6);
  align-items: center;
  max-width: var(--shell-wide);
  margin: 0 auto;
  padding: var(--sp-6) var(--sp-5);
}

.contact-band__title {
  margin: 0;
  font-size: var(--fs-2xl);
  letter-spacing: -0.02em;
}

.contact-band__body {
  margin: var(--sp-3) 0 0;
  max-width: 48ch;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.contact-band__note {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent);
}

.contact-band__cta {
  margin: 0;
}

/* One row, as in the mockup. Stacked vertically the three methods cost 176px
   and read as a list to scroll past rather than as a row of ways to get in
   touch. The band's own grid gives this column enough width to hold three. */
.contact-methods {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.contact-method {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  min-width: 0;
}

.contact-method__icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-elev);
  color: var(--accent);
}

.contact-method__glyph {
  width: 1.15rem;
  height: 1.15rem;
}

.contact-method__body {
  display: block;
  min-width: 0;
}

.contact-method__label {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.contact-method__value {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
  overflow-wrap: anywhere;
}

a.contact-method__value:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

.contact-method__note {
  display: block;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   13. Intents
   -------------------------------------------------------------------------- */

.intents {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
}

.intent {
  padding: var(--sp-5);
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-elev);
  box-shadow: var(--shadow-sm);
}

.intent h3,
.intent h2 {
  margin-bottom: var(--sp-3);
  font-family: var(--ff-display);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.intent p {
  color: var(--text-muted);
  font-size: var(--fs-base);
  line-height: 1.55;
}

.intent .btn {
  margin-top: var(--sp-2);
}

.intents--page {
  margin-bottom: var(--sp-7);
}

/* --------------------------------------------------------------------------
   14. Filters
   -------------------------------------------------------------------------- */

.filters {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  padding: var(--sp-5);
  margin-bottom: var(--sp-6);
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-elev);
}

.filter {
  margin: 0;
  padding: 0;
  border: 0;
}

.filter legend {
  padding: 0;
  margin-bottom: var(--sp-3);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.chip {
  padding: var(--sp-2) var(--sp-3);
  border: var(--border-w) solid var(--line-strong);
  border-radius: var(--radius-pill);
  background: var(--bg);
  color: var(--text-muted);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  transition: border-color 120ms ease-out, background-color 120ms ease-out,
    color 120ms ease-out;
}

.chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* Unpressed area chips take their programme colour, so the filter row doubles
   as the legend for the card rules. */
.chip[data-area]:not([aria-pressed="true"]) {
  color: var(--area-tone, var(--text-muted));
}

.chip[data-area]:not([aria-pressed="true"]):hover {
  border-color: var(--area-tone, var(--accent));
  color: var(--area-tone, var(--accent));
}

.chip[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.filters__status {
  grid-column: 1 / -1;
  margin: 0;
  padding-top: var(--sp-4);
  border-top: var(--border-w) solid var(--line);
  font-family: var(--ff-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.empty {
  margin-top: var(--sp-6);
  padding: var(--sp-6);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  text-align: center;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   15. Case study
   -------------------------------------------------------------------------- */

/* A three-track grid: the middle track is the reading column, and the sample
   wall spans all three. This is what keeps the prose centred while letting
   the work samples use the full width: a max-width on a child can never
   escape a narrower parent, so the parent has to be the wide thing. */
.case {
  display: grid;
  grid-template-columns: 1fr min(var(--measure), 100%) 1fr;
  max-width: var(--shell-wide);
  margin: 0 auto;
}

.case > * {
  grid-column: 2;
  /* A grid item's automatic minimum size is its min-content width, which can
     push a track wider than the page. Zeroing it is the standard guard. */
  min-width: 0;
}

.case > .cs-block--samples {
  grid-column: 1 / -1;
}

.case__head {
  margin-bottom: var(--sp-6);
}

.case__eyebrow {
  margin-bottom: var(--sp-4);
  font-size: var(--fs-sm);
}

.case__eyebrow a {
  color: var(--text-muted);
  text-decoration: none;
}

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

.case__summary {
  margin-top: var(--sp-5);
  font-size: var(--fs-lg);
  line-height: 1.5;
  color: var(--text-muted);
}

/* At a glance: a definition table, not a data table. */
.glance {
  width: 100%;
  margin-bottom: var(--sp-7);
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius);
  border-collapse: separate;
  border-spacing: 0;
  background: var(--bg-elev);
  overflow: hidden;
  font-size: var(--fs-base);
}

.glance caption {
  padding: var(--sp-4) var(--sp-5) 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
  text-align: left;
}

.glance th,
.glance td {
  padding: var(--sp-3) var(--sp-5);
  text-align: left;
  vertical-align: top;
  border-top: var(--border-w) solid var(--line);
}

.glance th {
  width: 38%;
  font-weight: 600;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.glance tr:first-child th,
.glance tr:first-child td {
  border-top: 0;
}

.cs-block {
  margin-bottom: var(--sp-6);
}

.cs-block h2 {
  margin-bottom: var(--sp-3);
  font-family: var(--ff-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.cs-block p {
  font-size: var(--fs-md);
}

.evidence {
  margin-top: var(--sp-4);
  padding: var(--sp-4);
  border-left: 3px solid var(--line-strong);
  background: var(--bg-inset);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: var(--fs-sm) !important;
  line-height: 1.6;
  color: var(--text-muted);
}

.evidence__label {
  display: block;
  margin-bottom: var(--sp-1);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-faint);
}

.cs-block--samples {
  margin-top: var(--sp-8);
  padding-top: var(--sp-6);
  border-top: var(--border-w) solid var(--line);
  max-width: none;
}

.cs-next {
  margin-top: var(--sp-7);
  padding-top: var(--sp-5);
  border-top: var(--border-w) solid var(--line);
  font-size: var(--fs-base);
}

/* --------------------------------------------------------------------------
   16. Work samples and the viewer
   -------------------------------------------------------------------------- */

.samples {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  align-items: start;
}

.sample {
  margin: 0;
}

/* Each sample is mounted like a sheet on a table, so pieces of different
   trim sizes sit together without either being distorted. */
.sample__open {
  display: block;
  width: 100%;
  padding: var(--sp-4);
  border: var(--border-w) solid var(--line);
  border-radius: var(--radius);
  background: var(--mount);
  box-shadow: var(--shadow-sm);
  cursor: zoom-in;
  transition: transform 160ms ease-out, box-shadow 160ms ease-out,
    border-color 160ms ease-out;
}

.sample__open:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow);
  border-color: var(--line-strong);
}

.sample__open img {
  display: block;
  width: 100%;
  height: auto;
  max-height: min(28rem, 58vh);
  object-fit: contain;
  border-radius: 2px;
}

/* A landscape infographic at one column is a thumbnail, not a sample: the text
   inside it cannot be read at 15rem wide. It spans two columns instead, and
   only where the grid actually has two to give. */
.sample[data-shape="wide"] {
  grid-column: span 1;
}

@media (min-width: 34rem) {
  .sample[data-shape="wide"] {
    grid-column: span 2;
  }
}

/* The opposite case: a panel exported at roughly 1:2.3 is taller than the
   column cap, so `contain` shrinks it to fit the height and leaves most of the
   mount empty on either side. Raising the cap for these lets the panel use the
   width it has. */
.sample[data-shape="tall"] .sample__open img {
  max-height: min(42rem, 80vh);
}

.sample figcaption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-1) var(--sp-3);
  margin-top: var(--sp-3);
  font-size: var(--fs-sm);
  line-height: 1.45;
}

.sample__caption {
  flex: 1 1 12rem;
  color: var(--text-muted);
}

.sample__format {
  font-family: var(--ff-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.03em;
  color: var(--text-faint);
  white-space: nowrap;
}

.viewer {
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--bg);
  color: var(--text);
}

.viewer::backdrop {
  background: var(--scrim);
}

.viewer[open] {
  display: flex;
  flex-direction: column;
}

.viewer__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border-bottom: var(--border-w) solid var(--line);
  background: var(--bg-elev);
}

.viewer__count {
  font-family: var(--ff-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.viewer__controls {
  display: flex;
  gap: var(--sp-2);
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border: var(--border-w) solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--text);
  font-family: var(--ff-body);
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
  transition: border-color 120ms ease-out, color 120ms ease-out,
    background-color 120ms ease-out;
}

.icon-btn:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.icon-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.icon-btn:disabled:hover {
  border-color: var(--line-strong);
  color: var(--text);
}

.viewer__stage {
  display: flex;
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--sp-5);
  background: var(--bg-sunken);
  overscroll-behavior: contain;
}

/* `margin: auto` rather than justify-content: centre. When the zoomed sample
   is wider than the stage, auto margins collapse to zero, so the left edge
   stays reachable instead of being clipped out of the scroll range. */
.viewer__stage img {
  display: block;
  flex: 0 0 auto;
  margin: auto;
  max-width: none;
  height: auto;
  background: var(--mount);
  border-radius: 2px;
  box-shadow: var(--shadow-lg);
}

.viewer__caption {
  margin: 0;
  padding: var(--sp-3) var(--sp-5);
  border-top: var(--border-w) solid var(--line);
  background: var(--bg-elev);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  text-align: center;
}

/* --------------------------------------------------------------------------
   17. About and CV
   -------------------------------------------------------------------------- */

.about {
  display: grid;
  gap: var(--sp-7) var(--sp-8);
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  align-items: start;
}

.about__narrative {
  max-width: var(--measure);
  font-size: var(--fs-md);
}

.about__narrative p:first-child {
  font-family: var(--ff-display);
  font-size: var(--fs-xl);
  line-height: 1.45;
  letter-spacing: -0.01em;
}

.about__cv {
  display: grid;
  gap: var(--sp-5);
}

.cv-block h2 {
  margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: var(--border-w) solid var(--line);
  font-family: var(--ff-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-muted);
}

.cv {
  display: grid;
  gap: var(--sp-4);
}

/* A row, not a stack, because four of the six dated entries carry the
   institution's logo. The logo is a flex sibling rather than a grid column, so
   an entry without one starts at the left edge instead of being indented by an
   empty track it never fills. */
.cv li {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
}

/* The text column. min-width: 0 is what lets the long detail lines wrap inside
   the flex row instead of forcing it wider than the block. */
.cv__body {
  display: grid;
  gap: 2px;
  min-width: 0;
}

/* A fixed box with object-fit, rather than a height, so every entry's text
   starts at the same x. These four logos run from 3.1:1 to 0.9:1, so sizing
   them by height alone would start each entry's text somewhere different, and
   letting the widest set the width would push the text too far right. Centring
   the artwork in the box is what stops the narrow emblem looking marooned. */
.cv__logo {
  flex: 0 0 auto;
  width: 80px;
  height: 24px;
  object-fit: contain;
  object-position: center;
  /* Sits on the period line rather than at the very top of the entry. */
  margin-top: 1px;
}

.cv__period {
  font-family: var(--ff-mono);
  font-size: var(--fs-xs);
  letter-spacing: 0.03em;
  color: var(--text-faint);
}

.cv__title {
  font-weight: 600;
  line-height: 1.35;
}

.cv__detail {
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-muted);
}

.plain {
  display: grid;
  gap: var(--sp-2);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text-muted);
}

.plain li {
  position: relative;
  padding-left: var(--sp-4);
}

.plain li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--line-strong);
}

.plain--contact {
  font-size: var(--fs-base);
  gap: var(--sp-3);
  max-width: var(--measure);
}

.plain--contact li::before {
  background: var(--accent);
}

.about__note {
  max-width: var(--measure);
  margin-top: var(--sp-7);
  padding-top: var(--sp-4);
  border-top: var(--border-w) solid var(--line);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   18. 404
   -------------------------------------------------------------------------- */

.notfound__code {
  margin-bottom: var(--sp-3);
  font-family: var(--ff-mono);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   20. Footer
   -------------------------------------------------------------------------- */

.site-footer {
  background: var(--bg-elev);
  border-top: var(--border-w) solid var(--line);
}

.site-footer__inner {
  max-width: var(--shell-wide);
  margin: 0 auto;
  padding: var(--sp-5) var(--sp-5);
}

.site-footer__top {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  flex-wrap: wrap;
  padding-bottom: var(--sp-5);
  border-bottom: var(--border-w) solid var(--line);
}

.footer-nav {
  margin-left: auto;
}

.footer-nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-nav a {
  color: var(--text-muted);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
}

.footer-nav a:hover {
  color: var(--accent);
}

.socials {
  display: flex;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.social {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border: var(--border-w) solid var(--line);
  border-radius: 50%;
  color: var(--text-muted);
  transition: border-color 120ms ease-out, color 120ms ease-out;
}

.social:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.social__icon {
  width: 1.1rem;
  height: 1.1rem;
}

.site-footer__bottom {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
  padding-top: var(--sp-5);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

.site-footer__rights {
  margin: 0;
}

/* margin:auto on both sides centres the tagline in whatever space is left,
   without a fixed basis that would break when the rights line wraps. */
.site-footer__tagline {
  margin: 0 auto;
  font-weight: 600;
}

.site-footer__tools {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin: 0;
}

.back-to-top {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: 600;
  text-decoration: none;
}

.back-to-top:hover {
  color: var(--accent);
}

.back-to-top__icon {
  flex: 0 0 auto;
  width: 1rem;
  height: 1rem;
}

.provenance {
  max-width: 88ch;
  margin: var(--sp-4) 0 0;
  padding-top: var(--sp-4);
  border-top: var(--border-w) solid var(--line);
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   21. Responsive
   -------------------------------------------------------------------------- */

/* The header's seven anchors, the language switch and the call to action stop
   fitting comfortably well before the hero does, so the drawer arrives first. */
/* 72rem, not 68rem. At 68rem the full navigation reappeared at 1089px while the
   header still needed 1131px, so every width from 1089 to 1131 overflowed by up
   to 42px. The sweep tested 1024 and 1440 and missed the whole band, which is
   why it survived: a breakpoint boundary needs a width on BOTH sides of it, and
   the failing side was the one nobody measured. */
@media (max-width: 72rem) {
  .site-header__inner {
    flex-wrap: wrap;
    gap: var(--sp-3);
  }

  .nav-toggle {
    display: inline-flex;
  }

  .site-nav {
    display: none;
    order: 3;
    width: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-4);
    padding: var(--sp-4) 0 var(--sp-2);
    border-top: var(--border-w) solid var(--line);
  }

  .site-nav[data-open="true"] {
    display: flex;
  }

  .site-nav__list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .site-nav__link {
    padding: var(--sp-3) var(--sp-2);
    font-size: var(--fs-base);
  }

  .site-nav__actions {
    flex-wrap: wrap;
    gap: var(--sp-3);
  }
}

/* The hero drops its third column before anything else does: the aside card is
   the least load-bearing part of the first screen. */
@media (max-width: 80rem) {
  .hero__inner {
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  }

  .hero__aside {
    grid-column: 1 / -1;
  }
}

@media (max-width: 64rem) {
  .hero__inner {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-5);
  }

  .band__inner {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-5);
  }

  .stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .band__quote {
    padding-top: var(--sp-4);
    padding-left: 0;
    border-top: 3px solid var(--band-line);
    border-left: 0;
  }

  .band__inner--split,
  .contact-band__inner {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-6);
  }
}

@media (max-width: 46rem) {
  :root {
    --fs-3xl: clamp(1.85rem, 1.2rem + 4vw, 2.5rem);
    --fs-2xl: 1.5rem;
  }

  .main {
    padding: var(--sp-5) var(--sp-4) var(--sp-7);
  }

  /* The about page was the only two-column grid that never collapsed. At 320 px
     its CV column was about 93 px wide, which was survivable while the entries
     were bare text. It is not now that each entry leads with an 80 px logo: the
     text column was left a few pixels and the page ran wider than the viewport,
     which is what the sweep caught. Collapsed at 46rem rather than 64rem,
     because at 1024 px the two columns still have room. */
  .about {
    grid-template-columns: minmax(0, 1fr);
  }

  .site-header__inner {
    padding: var(--sp-3) var(--sp-4);
  }

  /* Every full-bleed band carries its own gutter, so they have to be tightened
     together or the sections stop lining up with the header. */
  .hero,
  .band__inner,
  .band__inner--split,
  .contact-band__inner,
  .site-footer__inner {
    padding-right: var(--sp-4);
    padding-left: var(--sp-4);
  }

  /* The width is deliberately not set here: --band already narrows the
     flourish to the gutter, and a second cap only reintroduced the problem it
     was fixing. Only the type size drops. */
  .hero__script {
    font-size: 1.05rem;
  }

  .hero__quote {
    right: 0;
    bottom: var(--sp-3);
    max-width: 10rem;
    padding: var(--sp-2) var(--sp-3);
    font-size: var(--fs-xs);
  }

  .tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .projects,
  .services,
  .contact-methods,
  .stats {
    grid-template-columns: minmax(0, 1fr);
  }

  .stats {
    gap: var(--sp-4);
  }

  /* Two columns rather than four. This used to reset space-between to
     flex-start so wrapped items read left to right; the list is a grid now, so
     the step is expressed as the column count itself. */
  .partners__list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .footer-nav {
    width: 100%;
    margin-left: 0;
  }

  .site-footer__tagline {
    margin: 0;
  }

  .glance th {
    width: 34%;
    padding: var(--sp-3) var(--sp-4);
  }

  .glance td {
    padding: var(--sp-3) var(--sp-4);
  }

  .glance caption {
    padding: var(--sp-3) var(--sp-4) 0;
  }

  .filters {
    padding: var(--sp-4);
  }

  .viewer__bar {
    padding: var(--sp-2) var(--sp-3);
  }

  .viewer__stage {
    padding: var(--sp-3);
  }
}

/* One column. At two columns a 320px screen leaves each institution about
   124px, which breaks the longer names into four or five lines; one column
   gives the name a readable measure and the logo room to keep its height. */
@media (max-width: 28rem) {
  .partners__list {
    grid-template-columns: minmax(0, 1fr);
  }

  /* The hero is the tallest block above the fold, and below 28rem the
     proposition alone runs to five lines. These five reductions buy back about
     60px, which is what keeps the primary call to action on screen at 320 x 568.
     PRD criterion HP-01 only requires 360 x 640, where there is comfortable
     room; 320 is the narrowest width the sweep tests, and there the action
     falling off the bottom reads as a broken page rather than as a small one.
     The measurements this was tuned against are in HANDOVER section 3.2. */
  .hero {
    padding-top: var(--sp-3);
  }

  .hero .eyebrow {
    margin-bottom: var(--sp-2);
  }

  .hero__proposition {
    font-size: var(--fs-md);
  }

  .hero__supporting {
    margin-top: var(--sp-2);
  }

  .hero__actions {
    margin-top: var(--sp-4);
  }
}

/* --------------------------------------------------------------------------
   21. Motion and print
   -------------------------------------------------------------------------- */

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

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .card:hover,
  .btn:hover,
  .sample__open:hover {
    transform: none;
  }
}

@media print {
  .site-header,
  .site-footer,
  .demo-notice,
  .viewer,
  .filters,
  .skip-link,
  .hero__actions {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
    font-size: 11pt;
  }

  .main {
    max-width: none;
    padding: 0;
  }

  .card,
  .intent,
  .glance,
  .sample__open {
    box-shadow: none;
    border-color: #999;
  }

  .case,
  .about__narrative,
  .page-head {
    max-width: none;
  }

  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    color: #555;
  }
}
