/* ==========================================================================
   Colby Otte — site styles
   Every value comes from tokens.css. Mobile first; the one breakpoint is
   48rem (see the note in tokens.css). Nothing page-specific lives here.
   ========================================================================== */

/* --- Reset ----------------------------------------------------------------- */

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

* { margin: 0; }

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

body {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  -webkit-font-smoothing: antialiased;
}

main { flex: 1; }

img, svg, video, canvas, picture {
  display: block;
  max-width: 100%;
  height: auto;
}

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

button { cursor: pointer; }

a, button {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent; /* hover and focus states carry feedback */
}

/* In-page anchor targets stop short of the viewport edge. */
[id] { scroll-margin-top: var(--space-m); }

/* The attribute must win over any display rule in this file. */
[hidden] { display: none !important; }

/* --- Base typography --------------------------------------------------------
   tokens.css sets the families, weights, leading, and colors. This adds the
   scale and the spacing between blocks.                                     */

h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-4); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }

h2, h3, h4 { line-height: var(--leading-snug); }

p, ul, ol, blockquote { margin-block-end: var(--space-s); }

:where(p, ul, ol, blockquote, figure) + :where(h2, h3, h4) {
  margin-block-start: var(--space-l);
}

:where(h1, h2, h3, h4) { margin-block-end: var(--space-xs); }

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

a {
  text-decoration-skip-ink: auto;
  transition: color var(--dur-fast) var(--ease-out);
}

/* The amber underline is for links on light ground. On the dark grounds
   the link sets its own colour, and the underline follows it. */
:where(.site-header, .site-footer, .band-ink, .case) a { text-decoration-color: currentColor; }

/* --- Layout ------------------------------------------------------------------
   .wrap is the one container. Its side padding respects the notch on phones
   held sideways.                                                            */

.wrap {
  width: 100%;
  max-width: calc(var(--container) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline:
    max(var(--gutter), env(safe-area-inset-left))
    max(var(--gutter), env(safe-area-inset-right));
}

/* Text sections: same container, so they share the hero's left edge, with
   the text width capped on the contents instead of narrowing and centring
   the container. :where() keeps it weightless, so the 68ch paragraph
   measure from tokens.css and component widths like .lead still apply. */
:where(.wrap--text) > * { max-width: var(--container-text); }

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

@media (min-width: 48rem) {
  .section { padding-block: var(--space-2xl); }
}

/* Stacked sections share one gap, not two. */
.section + .section { padding-block-start: 0; }

/* --- Skip link ---------------------------------------------------------------- */

.skip-link {
  position: absolute;
  top: var(--space-2xs);
  left: var(--space-2xs);
  z-index: 10;
  padding: var(--space-xs) var(--space-s);
  background: var(--bg-surface);
  color: var(--text-primary);
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  transform: translateY(-200%);
}

.skip-link:focus { transform: none; }

/* --- Site header ---------------------------------------------------------------
   Mobile: wordmark and phone on one line, menu button beside them, nav
   dropping below when opened. Desktop: one row, nav always shown.
   The nav only collapses when JS is present (.js on <html>), so without JS
   it is a plain visible list.                                               */

/* Navy ground with the amber bar above it, bookending the navy footer. The
   ink rule that used to separate the header from the page isn't needed once
   the header has a ground of its own. */
.site-header {
  border-block-start: var(--rule-brand) solid var(--rule-accent);
  background: var(--bg-inverse-alt);
  color: var(--text-on-inverse);
}

.site-header .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 var(--space-m);
  padding-block: var(--space-xs);
}

.wordmark {
  margin-inline-end: auto;
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: var(--step-1);
  letter-spacing: var(--tracking-display);
  color: var(--text-on-inverse);
  text-decoration: none;
}

.wordmark,
.header-phone {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
}

/* Phone numbers never break at their hyphens. */
a[href^="tel:"] { white-space: nowrap; }

.header-phone {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  color: var(--text-on-inverse);
  text-decoration: none;
}

/* Not amber on hover: amber on navy is 4.2:1, under AA for text this size. */
.wordmark:hover,
.header-phone:hover { color: var(--bg-surface); }

.header-phone:hover { text-decoration: underline; }

.site-header :focus-visible { outline-color: var(--text-on-inverse); }

.nav-toggle {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding-inline: var(--space-s);
  background: none;
  border: var(--rule-hairline) solid var(--text-muted-on-inverse);
  border-radius: var(--radius-none);
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: var(--step--1);
  color: var(--text-on-inverse);
}

.nav-toggle:hover { border-color: var(--text-on-inverse); }

.site-nav { flex-basis: 100%; }

.site-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  border-block-start: var(--rule-hairline) solid var(--text-muted-on-inverse);
  margin-block-start: var(--space-xs);
}

.site-nav li { border-block-end: var(--rule-hairline) solid var(--text-muted-on-inverse); }

.site-nav li:last-child { border-block-end: 0; }

.site-nav a {
  display: flex;
  align-items: center;
  min-height: var(--tap-min);
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  color: var(--text-on-inverse);
  text-decoration: none;
}

.site-nav a:hover { color: var(--bg-surface); text-decoration: underline; }

.site-nav a[aria-current="page"] {
  text-decoration: underline;
  text-decoration-thickness: var(--rule-heavy);
}

.js .site-nav { display: none; }
.js .nav-toggle[aria-expanded="true"] ~ .site-nav { display: block; }

@media (min-width: 48rem) {
  .site-header .wrap { flex-wrap: nowrap; }

  .nav-toggle { display: none; }

  .site-nav,
  .js .site-nav { display: block; flex-basis: auto; }

  .site-nav ul {
    display: flex;
    gap: var(--space-m);
    margin: 0;
    border: 0;
  }

  .site-nav li { border: 0; }
}

/* --- Site footer ----------------------------------------------------------------
   Muted and small. Links in ink rather than amber so the footer never spends
   a page's amber budget.                                                    */

/* Navy ground, so a page ends in color instead of trailing off. No top
   margin: the footer has its own ground, and the last section already
   carries its own bottom padding. A margin here would show a strip of page
   ground between a dark closing band and the footer. */
.site-footer {
  background: var(--bg-inverse-alt);
  padding-block: var(--space-l) max(var(--space-l), env(safe-area-inset-bottom));
  font-size: var(--step--1);
  color: var(--text-muted-on-inverse);
}

.site-footer p { margin-block-end: var(--space-xs); }

.site-footer a { color: var(--text-on-inverse); }
/* Not amber: amber on navy is 4.2:1, under AA for text this size. */
.site-footer a:hover { color: var(--bg-surface); }
.site-footer :focus-visible { outline-color: var(--text-on-inverse); }

.footer-name {
  margin-block-end: 0;
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  font-size: var(--step-0);
  color: var(--text-on-inverse);
}
.footer-owner { margin-block-end: var(--space-xs); }

.footer-contact { font-variant-numeric: tabular-nums; }

.footer-nav ul {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-m);
}

.footer-nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
}

/* --- Inverted band -------------------------------------------------------------------
   A closing section on ink, the same language as the case study pages: it
   gives a light page a dark anchor at the end. Amber is legal as text here
   (5.65:1 on ink) but is spent sparingly — see the per-page budget.     */

.band-ink {
  background: var(--bg-inverse);
  color: var(--text-on-inverse);
}

/* Contour lines behind an inverted band. Faint enough to be texture rather
   than pattern — the contact details stay the thing you see. */
.has-contours { position: relative; overflow: hidden; }
.has-contours > .wrap { position: relative; z-index: 1; }

.contours {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--text-on-inverse);
  opacity: var(--texture-contour-opacity);
}

/* The SVG stretches to fill each band; without this its strokes thin out
   and fade as the band gets shorter or narrower. */
.contours path { vector-effect: non-scaling-stroke; }

.band-ink :where(h1, h2, h3) { color: var(--text-on-inverse); }
.band-ink .eyebrow { color: var(--text-muted-on-inverse); }
.band-ink a { color: var(--accent-on-ink); }
.band-ink a:hover { color: var(--text-on-inverse); }
.band-ink :focus-visible { outline-color: var(--text-on-inverse); }

.band-ink .contact-lines a { color: var(--text-on-inverse); }
/* Hover thickens the underline rather than turning amber: amber over a
   contour line falls below 4.5:1. */
.band-ink .contact-lines a:hover {
  color: var(--text-on-inverse);
  text-decoration-thickness: var(--rule-heavy);
}

.band-ink .button {
  background: var(--text-on-inverse);
  border-color: var(--text-on-inverse);
  color: var(--text-primary);
}

.band-ink .button:hover { background: var(--bg-surface); border-color: var(--bg-surface); }

/* --- Buttons --------------------------------------------------------------------
   Primary: solid navy. Secondary: hairline outline. Square corners, sentence
   case, never two primaries side by side.                                   */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding: var(--space-2xs) var(--space-m);
  border: var(--rule-hairline) solid var(--bg-inverse-alt);
  border-radius: var(--radius-none);
  background: var(--bg-inverse-alt);
  color: var(--text-on-inverse);
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: var(--step-0);
  line-height: var(--leading-tight);
  text-decoration: none;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.button:hover {
  background: var(--bg-inverse);
  border-color: var(--bg-inverse);
  color: var(--text-on-inverse);
}

.button--secondary {
  background: transparent;
  color: var(--text-secondary);
}

.button--secondary:hover {
  background: transparent;
  border-color: var(--border-ink);
  color: var(--text-primary);
}

.button:focus-visible { outline-offset: 3px; }

/* A row of buttons or a button plus a quieter text link. Wraps on phones. */
.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs) var(--space-m);
}

/* --- Section header --------------------------------------------------------------
   Rule, then eyebrow, then heading. The rule is ink by default; add
   .section-head--marked for the amber version, which counts toward the
   page's three amber uses.

   <header class="section-head">
     <p class="eyebrow">Legend</p>
     <h2>What I do</h2>
   </header>                                                                 */

.section-head {
  border-block-start: var(--rule-hairline) solid var(--border-ink);
  padding-block-start: var(--space-s);
  margin-block-end: var(--space-m);
}

.section-head--marked { border-block-start-color: var(--rule-accent); }

.section-head > :last-child { margin-block-end: 0; }

/* An eyebrow sits tight against the heading it labels. */
.eyebrow + :where(h1, h2, h3, h4) { margin-block-start: 0; }

/* Eyebrow led by an amber map dot. One amber use. */
.eyebrow--marked {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

.marker {
  flex: none;
  width: var(--space-2xs);
  height: var(--space-2xs);
  border-radius: 50%;
  background: var(--color-accent);
}

/* --- Hero ----------------------------------------------------------------------
   Left-aligned, text only. One primary button at most, then a quieter link. */

/* One step down from the default h1 on phones, so a sentence-length
   headline does not run four or five lines deep. */
.hero h1 {
  max-width: 20ch;
  font-size: var(--step-4);
}

/* Home: the words, then a band of map running the full width of the
   window. The hero gives up its bottom padding to the band so the two
   read as one opening. Nothing sits over the image; the caption gets its
   own solid ink bar, aligned to the container. */
.hero--band { padding-block-end: var(--space-l); }

/* From 1280px: pull the band up so it starts within the first screen
   (about 560px down at 1920×1080). Tighter space above and below the
   words, and the headline sets in two balanced lines instead of three —
   same size, wider measure. */
@media (min-width: 80rem) {
  .hero--band { padding-block: var(--space-xl) var(--space-m); }
  .hero--band h1 { max-width: none; }
  .hero--band .actions { margin-block-end: 0; }
}

.map-band { margin: 0; }
.map-band img { width: 100%; }

.map-band figcaption {
  background: var(--bg-inverse);
  color: var(--text-on-inverse);
  font-size: var(--step--1);
  line-height: var(--leading-snug);
  padding-block: var(--space-xs);
}

@media (min-width: 48rem) {
  .hero h1 { font-size: var(--step-5); }
}

.lead {
  font-size: var(--step-1);
  line-height: var(--leading-snug);
  max-width: 38ch;
  margin-block-end: var(--space-m);
}

/* --- Legend list ------------------------------------------------------------------
   Numbered entries under a legend-style header, divided by hairlines — the
   map-key convention in place of icon cards. Numerals use the text-safe
   amber: full-strength amber fails contrast as text on warm white.        */

.legend {
  list-style: none;
  padding: 0;
  margin-block-end: var(--space-l);
}

.legend-item {
  padding-block: var(--space-m);
  border-block-end: var(--rule-hairline) solid var(--border-subtle);
}

.legend-item:first-child { padding-block-start: 0; }

.legend-item h3 {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  font-size: var(--step-2);
  margin-block-end: var(--space-2xs);
}

.legend-item h3 a { min-width: 0; color: inherit; text-decoration: none; }
.legend-item h3 a:hover { text-decoration: underline; }

.legend-num {
  flex: none;
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  color: var(--text-accent);
}

.legend-item p {
  margin-block-end: 0;
  padding-inline-start: calc(2ch + var(--space-xs));
}

@media (min-width: 48rem) {
  .legend {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: var(--space-xl);
  }

  /* Both cells of the first row start flush with the section rule. */
  .legend-item:nth-child(-n + 2) { padding-block-start: 0; }
}

/* Compact legend (home page): one line per service, set a step smaller and
   tighter than the full legend, so it reads as a key to the work above it
   rather than competing with it. */
.legend--compact .legend-item { padding-block: var(--space-s); }
.legend--compact .legend-item h3 { font-size: var(--step-1); margin-block-end: var(--space-3xs); }

/* Single column at every width — for sequences read top to bottom. */
@media (min-width: 48rem) {
  .legend--single { display: block; }
  .legend--single .legend-item:nth-child(2) { padding-block-start: var(--space-m); }
}

/* --- Service pages ------------------------------------------------------------------ */

.service-figure { margin-block-start: var(--space-xl); }

.service-figure img {
  max-height: min(70vh, 36rem);
  width: auto;
  border: var(--rule-hairline) solid var(--border-subtle);
  background: var(--bg-surface);
}

.service-figure figcaption {
  margin-block-start: var(--space-xs);
  font-size: var(--step--1);
  color: var(--text-muted);
}

/* Term / definition pairs: formats, price factors. */
.terms { margin-block-end: var(--space-s); }

.terms dt {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  padding-block-start: var(--space-s);
  border-block-start: var(--rule-hairline) solid var(--border-subtle);
}

.terms dt:first-child { border-block-start: 0; padding-block-start: 0; }

.terms dd {
  margin: var(--space-3xs) 0 var(--space-s);
  max-width: var(--measure);
}

.faq h3 { font-size: var(--step-1); }
.faq h3:not(:first-child) { margin-block-start: var(--space-l); }

.gallery--after-cases { margin-block-start: var(--space-xl); }

.section-head--spaced { margin-block-start: var(--space-xl); }

/* --- About ---------------------------------------------------------------------------- */

/* Phones: portrait, then the text, then the timeline. The side column
   dissolves into the grid so its two parts can sit either side of the
   text; only the portrait moves, and it holds nothing to tab to. */
.about { display: grid; gap: var(--space-l); }
.about-side { display: contents; }
.about-portrait { order: -1; }

.about-portrait img { width: 100%; }

.about-section { margin-block-start: var(--space-xl); }

/* From 900px: portrait and timeline on the left, held in view while the
   text scrolls past; everything else on the right. */
@media (min-width: 56.25rem) {
  .about {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    column-gap: var(--space-xl);
    align-items: start;
  }
  .about-side {
    display: block;
    grid-row: 1;
    grid-column: 1;
  }
  .about-main { grid-column: 2; }
  .about-background { margin-block-start: var(--space-l); }

  /* Held to half the window so the portrait and the whole timeline fit in
     view together; the crop keeps the face. */
  .about-portrait img {
    max-height: 50vh;
    object-fit: cover;
    object-position: 50% 30%;
  }
}

/* Sticky only where the whole column fits in the window (half for the
   portrait, about 22rem for the timeline and spacing). On a shorter
   window it scrolls with the page rather than hiding its last lines. */
@media (min-width: 56.25rem) and (min-height: 45rem) {
  .about-side {
    position: sticky;
    top: var(--space-l);
  }
}

/* A side note to the page, so its heading steps down from the h2 size. */
.about-background h2 { font-size: var(--step-2); }

/* Compact: years in a column of their own that lines up, the role beside. */
.timeline {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: var(--space-s);
  margin: 0;
  font-size: var(--step--1);
  line-height: var(--leading-snug);
}

.timeline dt,
.timeline dd {
  margin: 0;
  padding-block: var(--space-2xs);
  border-block-start: var(--rule-hairline) solid var(--border-subtle);
}

.timeline dt:first-of-type,
.timeline dt:first-of-type + dd { border-block-start: 0; padding-block-start: 0; }

.timeline dt {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}

/* --- Proof line and contact block ----------------------------------------------- */

/* Home: the proof line as a pull quote, a step-3 serif set off by an ink
   rule. Its section has the same padding top and bottom, and the legend
   section before it drops its bottom padding, so the gaps above and below
   the quote match at every width. The rule is ink, not amber. */
.section--flush-end { padding-block-end: 0; }
.section--flush-end .legend { margin-block-end: 0; }
.section.section--quote { padding-block: var(--space-xl); }

.proof {
  margin: 0;
  max-width: 28ch;
  padding-inline-start: var(--space-m);
  border-inline-start: var(--rule-heavy) solid var(--border-ink);
  font-size: var(--step-3);
  line-height: var(--leading-snug);
  color: var(--text-secondary);
  text-wrap: balance;
}

.contact-lines {
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: var(--step-2);
  line-height: var(--leading-snug);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere; /* the email address on a 320px screen */
}

.contact-lines a {
  display: inline-block;
  padding-block: var(--space-3xs);
  color: var(--text-primary);
  text-decoration-thickness: var(--rule-hairline);
}

.contact-lines a:hover { color: var(--text-secondary); }

/* --- Work index -------------------------------------------------------------------
   Filters are plain links; the current one carries the heavy ink underline.
   Case card images and gallery tiles share one shape (--ratio-card) and the
   image fills it. A piece aims its crop with --focus, set inline from the
   data; logos and anything else that must show whole use fit: contain.   */

.filters ul {
  list-style: none;
  padding: 0;
  margin-block: var(--space-m) var(--space-xl);
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-m);
}

.filters a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  border-block-end: var(--rule-heavy) solid transparent;
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  text-decoration: none;
}

.filters a:hover { color: var(--text-secondary); border-block-end-color: var(--border-subtle); }
.filters a[aria-current="page"] { border-block-end-color: var(--border-ink); }

.work-tier + .work-tier { margin-block-start: var(--space-xl); }

.case-grid,
.gallery {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-l) var(--space-m);
}

/* Case studies: one column, then two from 700px, so four cards make a 2×2. */
.case-grid { grid-template-columns: minmax(0, 1fr); }

@media (min-width: 43.75rem) {
  .case-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Phones: two columns, so the gallery reads as a body of work rather than a
   single file of pictures. Featured pieces and the series tile take both,
   and titles drop a step to fit a half-width tile. From 700px the columns
   size themselves. */
.gallery {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--space-s);
}
.gallery-item--featured { grid-column: 1 / -1; }
.gallery .gallery-title { font-size: var(--step--1); }

@media (min-width: 43.75rem) {
  .gallery {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
    column-gap: var(--space-m);
  }
  .gallery-item--featured { grid-column: auto; }
  .gallery .gallery-title { font-size: var(--step-0); }
}

.case-card a { display: block; color: inherit; text-decoration: none; }
.case-card a:hover h3 { text-decoration: underline; }
.case-image { margin-block-end: var(--space-s); }
.case-card h3 { font-size: var(--step-2); }
.case-outcome { margin-block-end: 0; }

/* Each card's four parts (image, eyebrow, title, outcome) sit on rows shared
   across the grid, so a title that wraps to two lines pushes its neighbour's
   outcome down with it rather than leaving the row ragged. The fixed image
   shape already lines up the eyebrows; this lines up everything after.     */
@supports (grid-template-rows: subgrid) {
  .case-card,
  .case-card a {
    display: grid;
    grid-row: span 4;
    grid-template-rows: subgrid;
    row-gap: 0;
  }
  .case-card a > * { margin-block-start: 0; }
}

.case-image img,
.gallery-frame img,
.series-cover img {
  width: 100%;
  aspect-ratio: var(--ratio-card);
  object-fit: cover;
  object-position: var(--focus, center);
}

.gallery-frame {
  display: block;
  border: var(--rule-hairline) solid var(--border-subtle);
  transition: border-color var(--dur-fast) var(--ease-out);
}

.gallery-frame:hover { border-color: var(--border-ink); }

/* Shown whole: the white of the surface fills the space the piece doesn't,
   which matches the white ground logos are drawn on. */
.gallery-frame--contain { background: var(--bg-surface); }
.gallery-frame--contain img { object-fit: contain; }

/* A featured piece takes a 2×2 block on wider screens; dense packing lets
   the pieces after it fill the gap it would otherwise leave. */
@media (min-width: 56.25rem) {
  .gallery { grid-auto-flow: dense; }
  .gallery-item--featured { grid-column: span 2; grid-row: span 2; }
}

.gallery figcaption {
  margin-block-start: var(--space-xs);
  font-size: var(--step--1);
  line-height: var(--leading-snug);
}

.gallery .eyebrow { display: block; margin-block-end: var(--space-3xs); }

.gallery-title {
  display: block;
  margin-block-end: var(--space-3xs);
  font-family: var(--font-display);
  font-size: var(--step-0);
  color: var(--text-primary);
}

/* Text for screen readers only — kept in the flow, clipped to nothing. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* A live site to go and look at — the strongest proof a web piece has. */
.gallery-link {
  display: block;
  margin-block-start: var(--space-2xs);
  font-family: var(--font-display);
  font-size: var(--step--1);
  font-weight: var(--weight-bold);
}

/* --- Gallery series ----------------------------------------------------------
   A run of related pieces (the historical-sites maps) collapsed into one
   featured tile: a native <details>, so it opens with a click, Enter or
   Space and needs no script. Closed, the summary looks like a gallery tile
   with a worded cue. Open, the tile takes the whole grid row, the cover
   steps aside (it's one of the pieces anyway), and the pieces follow as an
   ordinary nested gallery the viewer already knows how to open.          */

.gallery-series summary {
  display: block;
  cursor: pointer;
  list-style: none;
}
.gallery-series summary::-webkit-details-marker { display: none; }

.series-cover {
  display: block;
  border: var(--rule-hairline) solid var(--border-subtle);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.gallery-series summary:hover .series-cover { border-color: var(--border-ink); }

.series-text {
  display: block;
  max-width: var(--measure);
  margin-block-start: var(--space-xs);
  font-size: var(--step--1);
  line-height: var(--leading-snug);
}

.series-cue {
  display: block;
  margin-block-start: var(--space-2xs);
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  color: var(--text-primary);
  text-decoration: underline;
  text-decoration-color: var(--link-underline);
  text-underline-offset: var(--underline-offset);
}
.gallery-series summary:hover .series-cue { text-decoration-thickness: var(--rule-heavy); }

.gallery-series details:not([open]) .cue-open,
.gallery-series details[open] .cue-closed,
.gallery-series details[open] .series-cover { display: none; }

.gallery-series:has(details[open]) { grid-column: 1 / -1; grid-row: auto; }

.gallery--series { margin-block-start: var(--space-m); }

/* --- Photo band (location pages) -------------------------------------------------
   Photographs of the place, not artwork: they crop to a common frame rather
   than sitting matted on white the way gallery pieces do.                   */

.photo-band {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-m);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}

.photo-band img {
  width: 100%;
  height: auto;
  aspect-ratio: var(--ratio-photo);
  object-fit: cover;
}

.photo-band figcaption {
  margin-block-start: var(--space-xs);
  font-size: var(--step--1);
  line-height: var(--leading-snug);
  color: var(--text-muted);
}

/* --- Gallery viewer (js/lightbox.js) ----------------------------------------------
   Full-screen native <dialog> on ink. No open/close animation. The page
   behind stops scrolling while it is open.                                 */

html:has(.lightbox[open]) { overflow: hidden; }

.lightbox {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--bg-inverse);
  color: var(--text-on-inverse);
  overscroll-behavior: contain;
}

.lightbox[open] {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

.lightbox::backdrop { background: var(--bg-inverse); }

.lightbox-bar,
.lightbox-foot {
  display: flex;
  align-items: center;
  gap: var(--space-s);
  padding:
    var(--space-xs)
    max(var(--space-s), env(safe-area-inset-right))
    var(--space-xs)
    max(var(--space-s), env(safe-area-inset-left));
}

.lightbox-bar { justify-content: space-between; padding-block-start: max(var(--space-xs), env(safe-area-inset-top)); }

.lightbox-foot {
  background: var(--bg-inverse-alt);
  padding-block-end: max(var(--space-xs), env(safe-area-inset-bottom));
}

.lightbox-count {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: var(--step--1);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted-on-inverse);
}

.lightbox-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: var(--space-xs);
}

.lightbox-stage img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  background: var(--bg-surface);
}

.lightbox-caption {
  flex: 1;
  min-width: 0;
  font-size: var(--step--1);
  line-height: var(--leading-snug);
}

.lightbox-caption strong { display: block; font-family: var(--font-display); }

.lightbox button {
  flex: none;
  min-height: var(--tap-min);
  padding-inline: var(--space-s);
  background: transparent;
  border: var(--rule-hairline) solid var(--text-muted-on-inverse);
  border-radius: var(--radius-none);
  color: var(--text-on-inverse);
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: var(--step--1);
}

.lightbox button:hover { border-color: var(--text-on-inverse); }
.lightbox :focus-visible { outline-color: var(--text-on-inverse); }

/* Phones: caption on its own row above the two step buttons. */
@media (max-width: 47.99rem) {
  .lightbox-foot { flex-wrap: wrap; }
  .lightbox-caption { order: -1; flex-basis: 100%; }
  .lightbox .lightbox-prev, .lightbox .lightbox-next { flex: 1; }
}

/* --- Case study -------------------------------------------------------------------
   The one inverted page type: ink ground, the work near full bleed, chrome
   kept small. Captions sit in a solid navy bar under the image — never a
   scrim over it. Amber is legal as text only here (5.65:1 on ink).        */

.case {
  background: var(--bg-inverse);
  color: var(--text-on-inverse);
  padding-block-end: var(--space-xl);
}


.case :where(h1, h2, h3) { color: var(--text-on-inverse); }
.case .eyebrow { color: var(--text-muted-on-inverse); }
.case a { color: var(--accent-on-ink); }
.case a:hover { color: var(--text-on-inverse); }
.case :focus-visible { outline-color: var(--text-on-inverse); }

.case .button { background: var(--text-on-inverse); border-color: var(--text-on-inverse); color: var(--text-primary); }
.case .button:hover { background: var(--bg-surface); border-color: var(--bg-surface); color: var(--text-primary); }

/* One frame: title, text and images all sit in .wrap at the case width,
   so every edge on the page lines up. */
.case .wrap { max-width: calc(var(--container-case) + 2 * var(--gutter)); }

.case-head { padding-block: var(--space-xl) var(--space-l); }
.case-head h1 { margin-block-end: 0; }

/* Every image fills the frame's width; the caption bar runs under it. */
.case-figure img { width: 100%; }

.case-figure figcaption {
  background: var(--bg-inverse-alt);
  color: var(--text-on-inverse);
  font-size: var(--step--1);
  line-height: var(--leading-snug);
  padding: var(--space-xs) var(--space-s);
}

.case-body { padding-block: var(--space-xl) var(--space-l); }
.case-body section + section { margin-block-start: var(--space-l); }
.case-body .eyebrow { margin-block-end: var(--space-xs); }
.case-body p { font-size: var(--step-1); line-height: var(--leading-body); }
.case-text > :last-child { margin-block-end: 0; }

.case-details { display: grid; gap: var(--space-l); }

/* From 900px. Text reads like a map legend: the label in a margin column,
   the paragraphs beside it at their 68ch measure, a hairline over each.
   Portrait images take half the frame — side by side when two come
   together, or one in the left half with its caption bar under it —
   rather than running a full frame wide and several screens tall. */
@media (min-width: 56.25rem) {
  .case-body section {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 3fr);
    column-gap: var(--space-l);
    align-items: baseline;
    padding-block-start: var(--space-s);
    border-block-start: var(--rule-hairline) solid var(--text-muted-on-inverse);
  }
  .case-body .eyebrow { margin-block-end: 0; }

  .case-pair,
  .case-figure--portrait {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-l);
    align-items: start;
  }

  /* A lone portrait keeps its caption bar under it, in the left half. */
  .case-figure--portrait { row-gap: 0; }
  .case-figure--portrait > * { grid-column: 1; }
}

.case-foot { padding-block-start: var(--space-xl); }

.case-credit {
  font-size: var(--step--1);
  color: var(--text-muted-on-inverse);
  border-block-start: var(--rule-hairline) solid var(--text-muted-on-inverse);
  padding-block-start: var(--space-s);
  margin-block-end: var(--space-l);
}

/* --- Contact layout --------------------------------------------------------------
   Phone and email first — some people will never use a form. Side by side
   from 48rem, direct contact in the narrower right column.                */

.contact-layout {
  display: grid;
  gap: var(--space-xl);
  margin-block-start: var(--space-l);
}

.contact-direct {
  border-block-start: var(--rule-hairline) solid var(--border-ink);
  padding-block-start: var(--space-s);
}

.contact-area { font-size: var(--step--1); color: var(--text-muted); }

.form {
  border-block-start: var(--rule-hairline) solid var(--border-ink);
  padding-block-start: var(--space-s);
  max-width: var(--container-text);
}

@media (min-width: 48rem) {
  .contact-layout {
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    column-gap: var(--space-xl);
  }

  .contact-direct { grid-column: 2; grid-row: 1; }
  .form { grid-column: 1; grid-row: 1; }
}

/* --- Forms --------------------------------------------------------------------------
   Square fields on a white surface. The slate border clears the 3:1 needed
   for a field boundary; it is never used as text.                         */

.field { margin-block-end: var(--space-m); }

.field label {
  display: block;
  margin-block-end: var(--space-3xs);
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: var(--step--1);
  color: var(--text-primary);
}

.optional { font-weight: var(--weight-regular); color: var(--text-muted); }

.field input,
.field select,
.field textarea {
  display: block;
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-2xs) var(--space-xs);
  border: var(--rule-hairline) solid var(--border-strong);
  border-radius: var(--radius-none);
  background-color: var(--bg-surface);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: var(--leading-snug);
}

.field textarea { resize: vertical; min-height: calc(var(--tap-min) * 4); }

.field input:hover,
.field select:hover,
.field textarea:hover { border-color: var(--border-ink); }

.field :focus-visible { outline-offset: 0; border-color: var(--border-ink); }

/* Status after submit: solid bar, no amber. */
.notice {
  margin-block-end: var(--space-m);
  padding: var(--space-xs) var(--space-s);
  border-inline-start: var(--rule-heavy) solid var(--border-ink);
  background: var(--bg-surface);
  color: var(--text-primary);
}

.notice--error { border-inline-start-color: var(--text-secondary); font-weight: var(--weight-bold); }

/* Honeypot: off-screen rather than display:none, which some bots skip. */
.hp {
  position: absolute;
  left: -100vw;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.eyebrow {
  margin-block-end: var(--space-2xs);
  font-family: var(--font-display);
  font-weight: var(--weight-bold);
  font-size: var(--step--1);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-muted);
}
