/* ============================================================
   Heinrichs Online, site.css
   The new visual world. See DESIGN.md at the project root.

   THESIS      A plain warm page that earns its keep in small things. Its one
               device is precision: a labelled rule that ends exactly where the
               text beside it ends, measured rather than guessed.
   OWN-WORLD   One soft beige ground (#FAF8F4), a three-step warm brown ink
               ramp, one rose-magenta accent (#B84A70). Petrona light into bold
               italic, Figtree beneath. Flat, two shadows, pill buttons.
   STORY       She has done this for 35 years, she does it herself, the work
               is real and live, and talking to her costs nothing.
   FIRST VIEW  Three-line display left with the labelled rule under it, the
               action, and one line of credential; Shelly and her short story
               framed right. The live client work starts immediately below.
   FORM        Plain register, chosen by Shelly over six bolder worlds across
               seven rounds. Direction seed key 04754abf, then user-pinned.
   ============================================================ */

:root {
  /* colour */
  --beige:          #FAF8F4;
  --card:           #FFFFFF;
  --on-raspberry:   #FFF8F7;
  --raspberry:      #B84A70;
  --raspberry-deep: #A63F64;
  --ink:            #453E36;
  --ink-soft:       #5C5546;
  --ink-mute:       #6F6759;
  --hairline:       #EAE4D9;
  --hairline-warm:  #E3DCCE;

  /* The one dark surface on the site: the footer, and nowhere else. Warm-toned
     rather than neutral black so it belongs to the beige and brown above it. */
  --footer:         #1F1C1A;
  --on-footer:      #F6F1EA;
  --on-footer-soft: #BEB8AF;
  --on-footer-mute: #A39C92;

  /* type */
  --display: 'Petrona', Georgia, serif;
  --body:    'Figtree', system-ui, -apple-system, sans-serif;

  /* space */
  --s-xs: 4px;
  --s-sm: 8px;
  --s-md: 16px;
  --s-lg: 24px;
  --s-xl: 32px;
  --s-2xl: 48px;
  --s-head-gap: clamp(14px, 1.7vw, 24px);
  --s-section: clamp(26px, 3.4vw, 40px);
  --pad-side: clamp(20px, 5vw, 60px);
  --w-services: 720px;
  --work-card-pad: 8px;

  /* shape */
  --r-pill: 999px;
  --r-card: 14px;
  --r-image: 7px;
  --r-sm: 10px;

  /* type steps that are not roles: the UI step and the small body step */
  --t-ui: 14px;
  --t-body-sm: 15px;
  --t-mark: 17px;

  /* depth: the entire vocabulary, two shadows */
  --lift-content: 0 16px 36px -20px rgba(90, 70, 45, .42);
  --lift-action:  0 8px 18px -8px rgba(150, 50, 88, .42);

  --ease: cubic-bezier(.22, 1, .36, 1);
}

/* ---------- base ---------- */

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

html, body {
  margin: 0;
  /* clip, not hidden: contains any stray overflow without making a scroll
     container. The old site's 100vw wave divider shipped a real mobile bug. */
  overflow-x: clip;
}

body {
  background: var(--beige);
  color: var(--ink-soft);
  font-family: var(--body);
  font-size: 16.5px;
  line-height: 1.8;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { font-family: var(--display); color: var(--ink); margin: 0; font-weight: 400; }
p { margin: 0; }
img { max-width: 100%; display: block; }

a { color: var(--raspberry-deep); }

:focus-visible {
  outline: 2px solid var(--raspberry);
  outline-offset: 3px;
}

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--ink); color: var(--beige);
  padding: 12px 20px; z-index: 50; text-decoration: none;
  font-size: var(--t-ui);
}
.skip:focus { left: var(--s-md); top: var(--s-md); }

.wrap { max-width: 1120px; margin: 0 auto; padding: 0 var(--pad-side); }

/* ---------- type roles ---------- */

/* Display is the opening line of a page, and only that. It breaks where the
   markup says, not where the column runs out, so the lower bound only has to
   hold the longest fragment rather than the whole phrase. */
.display {
  font-size: clamp(28px, 6.4vw, 38px);
  line-height: 1.16;
  letter-spacing: -.004em;
  font-weight: 300;
}

/* The weight contrast itself, usable at any size. .finish takes its own line;
   .accent stays inline for headings that are one line. */
.setup  { display: block; font-weight: 300; }
.finish { display: block; font-weight: 600; font-style: italic; color: var(--raspberry); }
.accent { font-weight: 600; font-style: italic; color: var(--raspberry); }

.headline {
  font-size: clamp(24px, 3.9vw, 31px);
  line-height: 1.24;
  letter-spacing: -.006em;
  font-weight: 500;
}

.title { font-size: 19px; line-height: 1.35; font-weight: 500; }

.lede { color: var(--ink-soft); max-width: 62ch; }

.label {
  font-family: var(--body);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-mute);
}

.rule { height: 1px; background: var(--hairline); border: 0; margin: 0; }

/* ---------- the inset rule: the one signature component ---------- */

.measured {
  /* The label sits IN the rule, and the whole thing runs to the right edge of the
     longest line of the paragraph below it. That width is measured at runtime and
     lands here as --rule-w; the fallback keeps it sane if scripting never runs. */
  display: flex;
  align-items: center;
  gap: 12px;
  width: var(--rule-w, 100%);
  max-width: 100%;
  margin-top: var(--s-lg);
}
.measured .note {
  flex: none;
  font-size: 12.5px;
  letter-spacing: .035em;
  color: var(--ink-mute);
}
.measured .bar {
  flex: 1 1 auto;
  min-width: 0;
  height: 1px;
  background: var(--hairline-warm);
  transform-origin: left center;
  animation: draw 900ms var(--ease) both;
  /* nudged to the optical centre of the lowercase letters rather than the middle
     of the line box, which sits high because of the ascender space */
  transform: translateY(1px);
}
@keyframes draw {
  from { transform: translateY(1px) scaleX(0); }
  to   { transform: translateY(1px) scaleX(1); }
}

@media (prefers-reduced-motion: reduce) {
  .measured .bar { animation: none; }
  * { transition-duration: 1ms !important; }
}

/* ---------- buttons ---------- */

.btn {
  display: inline-flex; align-items: center; gap: 11px;
  font-family: var(--body); font-size: var(--t-ui); font-weight: 500;
  text-decoration: none; padding: 15px 30px;
  border: 0; border-radius: var(--r-pill); cursor: pointer;
  transition: background-color 180ms var(--ease), transform 180ms var(--ease);
}
.btn-primary {
  background: var(--raspberry); color: var(--on-raspberry);
  box-shadow: var(--lift-action);
}
.btn-primary:hover { background: var(--raspberry-deep); transform: translateY(-1px); }
.btn-primary:active { transform: translateY(0); }

.link-quiet {
  display: inline-block;
  font-size: var(--t-ui); font-weight: 500;
  color: var(--ink); text-decoration: none;
  border-bottom: 1px solid var(--raspberry);
  padding-bottom: 3px;
  transition: color 180ms var(--ease);
}
.link-quiet:hover { color: var(--raspberry-deep); }
.link-quiet .arrow { transition: transform 180ms var(--ease); display: inline-block; }
.link-quiet:hover .arrow { transform: translateX(3px); }

/* ---------- header ---------- */

.site-head { padding-top: 34px; }
.head-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s-lg); }
.mark {
  font-family: var(--display); font-size: var(--t-mark); letter-spacing: .012em;
  color: var(--ink); text-decoration: none;
  display: inline-flex; align-items: baseline; gap: 6px;
}
.mark .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--raspberry); }
.site-nav { display: flex; gap: 30px; }
.site-nav a {
  font-size: var(--t-ui); color: var(--ink-mute); text-decoration: none;
  transition: color 180ms var(--ease);
}
.site-nav a:hover { color: var(--ink); }
.site-nav a[aria-current='page'] { color: var(--ink); }
.site-head .rule { margin-top: 28px; }

/* ---------- hero ---------- */

/* .intro is the same opening as the homepage hero, used by the six subpages.
   One column unless it carries a portrait, in which case it takes .intro-grid
   and .intro-me and behaves exactly like the hero. */
.hero, .intro { padding: 56px 0 var(--s-section); }
/* The homepage hero keeps its own 1.12fr / .88fr. About's .intro-grid used to
   share this rule and no longer does: it joined the shared subpage grid further
   down the file so its columns line up with the sections beneath it. */
.hero-grid {
  display: grid; grid-template-columns: 1.12fr .88fr;
  gap: 64px; align-items: center;
}
/* The hero centres its two columns because the left one carries the action and
   the credential line, so it is nearly as tall as the portrait. A subpage intro
   is shorter, and centring it leaves the headline floating, so it starts at the
   top instead. That `align-items: start` now comes from the shared subpage grid
   rather than from a rule of its own here. */
.hero .lede, .intro-grid .lede { margin-top: var(--s-lg); max-width: 44ch; }
.intro .lede { margin-top: var(--s-lg); }

/* The five subpages without a portrait open as two columns: the display line on
   the left, the inset rule and the lede together on the right. They ran as one
   column until 2026-07-30, which left the right half of a 1000px row empty and
   read as a broken grid rather than as margin. Shelly raised it on the Services
   page and the same shape was wrong on all five.

   The ratio is NOT the hero's. It is shared with the four offers below it, in the
   .intro-open, .offer rule further down, because Shelly read the page on
   2026-07-30 with the two grids at different ratios and the right column visibly
   stepped sideways between the opening and the first offer. One geometry down the
   whole page was the point of the change, so the two selectors are deliberately
   bound together and must not be split back apart.

   One consequence to know about: DESIGN.md describes the inset rule as sitting
   under the display line. It now sits beside it. What the rule actually claims is
   unchanged, since it still measures itself against the lede directly beneath it,
   which is why the rule and the lede move as one block rather than separately. */
/* The column is the measure here, the same as the closing row and the offers. A
   52ch cap would stop short of a 412px column and leave the rule, which measures
   the real text, ending somewhere that looks arbitrary. */
.intro-narrow .lede { max-width: none; }
/* The rule now starts its own column rather than following the display line, so
   the gap that separated them belongs to the row, not to the rule. */
.intro-open .measured { margin-top: 0; }
.hero .btn-primary, .intro .btn-primary { margin-top: 30px; }
.hero .since {
  font-family: var(--display); font-size: var(--t-mark);
  line-height: 1.4; color: var(--ink);
  margin-top: var(--s-lg);
}

.hero-me, .intro-me { max-width: 380px; }
/* shelly.png is 320x400, a 4:5 portrait. The frame uses that exact ratio so the
   photo is never cropped. Any other ratio here cuts the top of her head off. */
.hero-me .portrait, .intro-me .portrait {
  background: var(--card); padding: 10px;
  border-radius: var(--r-card); box-shadow: var(--lift-content);
  max-width: 300px;
}
/* About's portrait is smaller than the homepage's, sized against the opening text
   beside it rather than against its column. Measured at 1280px: the text block is
   262px tall, and a 300px frame stood 479px including the caption, so the photo
   towered over the words it was meant to sit beside. The caption keeps the full
   380px of the aside, which is why the cap is on the frame and not on .intro-me:
   constrain the aside instead and the caption wraps to four lines and hands the
   height straight back.

   `height: auto` is load-bearing. The image element carries the 320 by 400 width
   and height attributes for layout stability, and the browser turns that height
   into a real CSS height that outranks `aspect-ratio`. Without this the photo
   stayed 400px tall at every width, so narrowing the frame cropped her out of the
   sides instead of scaling the photo down. The homepage hero still behaves that
   way and renders 280 by 400, which is 7:10 rather than the 4:5 this comment
   block claims. Left alone deliberately: changing it moves the homepage. */
/* 214px, not a round number, because it is solved rather than chosen. The frame
   is 10px of padding around a 4:5 image, so its height is 20 + (w - 20) * 1.25.
   Setting that equal to the 262px opening text block gives w = 213.6. At 214 the
   frame stands 262px and the two columns finish on the same line. */
.intro-me .portrait { max-width: 214px; }
.intro-me .portrait img { height: auto; }
.hero-me .portrait img, .intro-me .portrait img {
  border-radius: var(--r-image); width: 100%;
  aspect-ratio: 4 / 5; object-fit: cover; object-position: center top;
}
.hero-me .me-body, .intro-me .me-body {
  font-size: var(--t-body-sm); line-height: 1.7;
  color: var(--ink-soft); margin-top: var(--s-sm);
}
.hero-me .link-quiet, .intro-me .link-quiet { margin-top: var(--s-md); }

/* ---------- sections ---------- */

.section { padding: var(--s-section) 0; }

/* Closes the introduction, the way the header rule closes the masthead. Sits
   tight under the hero, then gives the heading below it room, so the rule reads
   as a chapter break rather than as part of the heading. */
.section-opens { padding-top: var(--s-md); }
.section-opens .rule { margin-bottom: var(--s-xl); }
.section-head { max-width: 52ch; }
.section-head .headline + .lede { margin-top: var(--s-md); }

/* what I do: a plain list, not a row of matching cards */
.services {
  margin-top: var(--s-head-gap);
  max-width: var(--w-services);
  border-top: 1px solid var(--hairline);
}
.service {
  display: grid; grid-template-columns: 250px 1fr;
  gap: var(--s-xs) var(--s-xl); align-items: baseline;
  padding: 17px 0;
  border-bottom: 1px solid var(--hairline);
}
.service .title { color: var(--ink); }
.service p { color: var(--ink-soft); }
/* Right edge of this link lines up with the right end of the hairline above it,
   which is why both are bound to --w-services rather than to separate numbers. */
.section-foot {
  max-width: var(--w-services);
  margin-top: var(--s-xl);
  display: flex;
  justify-content: flex-end;
}

/* recent work */
.work-grid {
  margin-top: var(--s-head-gap);
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-xl);
}
.work-item { display: flex; flex-direction: column; }
.work-item .shot {
  background: var(--card); padding: var(--work-card-pad);
  border-radius: var(--r-sm); box-shadow: var(--lift-content);
  transition: transform 220ms var(--ease);
}
.work-item .shot img { border-radius: var(--r-image); height: 190px; width: 100%; object-fit: cover; object-position: top center; }
.work-item a.shot-link { text-decoration: none; display: block; }
.work-item a.shot-link:hover .shot { transform: translateY(-3px); }
/* Indented by exactly the card's padding, so the text starts where the picture
   starts. Bound to the same token as the padding so the two cannot drift. */
.work-item .meta { margin-top: var(--s-md); padding-left: var(--work-card-pad); }
.work-item .kind { color: var(--ink-mute); font-size: 12.5px; }
.work-item .title { margin-top: 3px; color: var(--ink); }
.work-item .roles { margin-top: 6px; color: var(--ink-mute); font-size: 12.5px; }
.work-item .visit { margin-top: 10px; }

/* how it works */
.steps {
  margin-top: var(--s-head-gap);
  max-width: 860px;
  display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-xl) 48px;
}
.step { border-top: 1px solid var(--hairline-warm); padding-top: var(--s-md); }
.step .n {
  font-family: var(--display); font-size: 14px; font-style: italic;
  color: var(--raspberry-deep); display: block; margin-bottom: 10px;
}
.step .title { color: var(--ink); }
.step p { margin-top: var(--s-sm); font-size: var(--t-body-sm); line-height: 1.7; }

/* close */
.close { padding-bottom: var(--s-section); }
/* Left column deliberately narrow so the closing line falls on three, right one
   wide enough to hold the paragraph on two. That combination is what keeps the
   whole row short. The lede's own max-width is released here; the column is the
   measure. */
.close-grid { display: grid; grid-template-columns: .78fr 1.22fr; gap: 56px; align-items: end; }
.close .lede { max-width: none; }
.close .btn-primary { margin-top: var(--s-lg); }

/* footer: the page's only dark surface. The colour change is the boundary, so
   the footer takes no hairline of its own. */
.site-foot {
  background: var(--footer);
  color: var(--on-footer-soft);
  padding: var(--s-2xl) 0;
  margin-top: var(--s-section);
}
.site-foot .mark { color: var(--on-footer); }
/* Centred as of 2026-07-30. It used to be wordmark hard left, nav hard right,
   which is the masthead's arrangement; repeating it at the foot of the page made
   the two rows read as bookends around content that had already ended. Stacked
   and centred, the footer reads as a sign-off. */
.foot-row {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--s-md);
}
.foot-nav { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-lg); }
.foot-nav a, .site-foot a {
  font-size: var(--t-ui); color: var(--on-footer-soft); text-decoration: none;
  transition: color 180ms var(--ease);
}
.foot-nav a:hover, .site-foot a:hover { color: var(--on-footer); }
.site-foot :focus-visible { outline-color: var(--on-footer); }
/* `align-items: baseline` is the fix for a real bug, not a preference. This row
   mixes 14px links with the 12.5px copyright line, and a flex row defaults to
   `stretch`, which tops each item's box out at the same y. The smaller text then
   sits visibly higher than the links beside it. Shelly spotted it on 2026-07-30.
   Baseline sits them all on the same line, which is what the eye expects. */
.foot-legal {
  margin-top: var(--s-xl); padding-top: var(--s-lg);
  border-top: 1px solid rgba(246, 241, 234, .14);
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: center;
  gap: var(--s-md) var(--s-lg);
}
.site-foot .label { color: var(--on-footer-mute); }

/* ---------- responsive ---------- */

@media (max-width: 1024px) {
  /* stack the hero before the text column gets too narrow to hold the display
     line on two lines. Also covers the Georgia fallback during font swap, which
     is wider than Petrona and broke first. */
  .hero-grid { grid-template-columns: 1fr; gap: var(--s-xl); }
  .hero-me, .intro-me { max-width: 100%; }
  .hero-me .portrait, .intro-me .portrait { max-width: 300px; }
  /* .intro-open, .intro-grid, .section-two and .offer stack at this width too,
     but their rules live in the subpage block at the very bottom of the file.
     They have to: their base rule is declared below this media query, and a media
     query adds no specificity, so a copy here would silently lose to it. */
}

@media (max-width: 980px) {
  .work-grid { grid-template-columns: repeat(2, 1fr); }
  .hero-grid { gap: 44px; }
}

@media (max-width: 820px) {
  .hero, .intro { padding-top: 46px; }
  .hero-grid, .close-grid { grid-template-columns: 1fr; }
  .hero-grid { gap: var(--s-xl); }
  .hero-me, .intro-me { max-width: 100%; }
  .close-grid { gap: var(--s-lg); align-items: start; }
  .service { grid-template-columns: 1fr; gap: var(--s-xs); }
}

@media (max-width: 560px) {
  .head-row { flex-direction: column; align-items: flex-start; gap: var(--s-md); }
  .site-nav { gap: 20px; flex-wrap: wrap; }
  .work-grid { grid-template-columns: 1fr; gap: var(--s-xl); }
  .steps { grid-template-columns: 1fr; }
  .work-item .shot img { height: 210px; }
}

/* ============================================================
   Subpages. Everything below is shared by About, Services,
   Portfolio, Contact, Privacy and Terms. No new colours, no new
   shadows, no new radii: structure comes from the hairlines and
   the rhythm, the way it does on the homepage.
   ============================================================ */

/* ---------- prose ---------- */

/* A reading column. The measure is the 62 character body measure, and the
   subheads inside it use the same headline role as a section heading, so a
   long page still has only one type scale. */
/* 52ch, the same measure the homepage section ledes use. The 62ch body measure
   in DESIGN.md is a ch-unit figure: at 16.5px Figtree it renders about 78
   characters per line, which is too long to read down a whole page. */
.prose { max-width: 52ch; }
.section-head + .prose { margin-top: var(--s-head-gap); }
.prose p { color: var(--ink-soft); }
.prose p + p { margin-top: var(--s-md); }
.prose .headline { margin-top: var(--s-2xl); }
.prose .headline + p { margin-top: var(--s-md); }
.prose .lede { max-width: 62ch; }

/* One passage in the display face where the page needs her voice rather than
   another heading. Ink, not accent: the accent belongs to headings only. */
.passage {
  font-family: var(--display);
  font-size: 21px; line-height: 1.5; font-style: italic;
  color: var(--ink);
  max-width: 46ch;
}

/* The step numeral, lifted out of .step so the services list and the legal
   sections can use the same one. */
.numeral {
  font-family: var(--display); font-size: 14px; font-style: italic;
  color: var(--raspberry-deep); display: block; margin-bottom: 10px;
}

/* ---------- services page: the four offers ---------- */

/* Four offers, each one an editorial block: numeral, title, one passage in her
   voice, then the detail. The numeral carries the accent, the way it does in the
   homepage process steps, so the titles themselves stay ink.

   Each offer is a two-column spread rather than one narrow column down the left.
   Shelly read the single column against the 1120px wrap on 2026-07-30 and saw the
   empty right half as a broken grid, which it looked like. Nothing was added to
   fix it; the copy that was already there now uses the width. The naming, title
   and summary in .offer-head, detail in .body, follows .case-head on Portfolio.

   The ratio is .92fr / 1.08fr so the reading column is the wider of the two. At
   the 1120px cap that is 430px and 506px, and 506px measures about 61 characters,
   just inside the 62 character guidance in DESIGN.md.

   The subpage opening shares this exact geometry, which is why the two selectors
   are declared together below. It is one grid running down the page, not two that
   happen to look similar, so the inset rule, the lede and every detail paragraph
   all start on the same left edge. Editing the columns or the gap here moves the
   opening too, and that is intended. */
.offers { max-width: none; }
/* The one subpage grid. Every two-column row on Services, Portfolio, Contact,
   About and the legal pages is declared here so they cannot drift apart. */
.intro-open,
.intro-grid,
.section-two,
.offer {
  display: grid;
  grid-template-columns: .92fr 1.08fr;
  gap: var(--s-xl);
  align-items: start;
}
.offer {
  border-top: 1px solid var(--hairline);
  padding-top: var(--s-lg);
}
/* About's body sections: the heading takes the left column, its prose the right,
   which is the same split the offers use. Both measures are released because the
   column is the measure. */
.section-two .section-head { max-width: none; }
.section-two .prose { max-width: none; }
/* About's story is a full-width row under the opening, not a column beside the
   photo, so it gets the same two-column split as every other section. It draws no
   rule, so it needs the top padding of a section without the usual 32px the rule
   would have contributed below itself. */
.story-open { padding-top: 0; }
/* The section rule above has already closed the introduction, so the first offer
   does not draw a second hairline 32px under it. */
.offer:first-child { border-top: 0; padding-top: 0; }
.offer + .offer { margin-top: var(--s-2xl); }
.offer .headline { margin: 0; }
/* Both measures are released here on purpose: the column is the measure, the same
   way it is in the closing row. A 46ch passage and a 52ch body would each cap
   short of their column and leave a ragged right edge inside a fixed grid. */
.offer .passage { margin-top: var(--s-md); max-width: none; }
.offer .body { margin-top: 0; }
.offer .body p { color: var(--ink-soft); max-width: none; }
.offer .body p + p { margin-top: var(--s-md); }

/* ---------- faq ---------- */

.faq { margin-top: var(--s-head-gap); max-width: var(--w-services); border-top: 1px solid var(--hairline); }
.faq-item { padding: var(--s-lg) 0; border-bottom: 1px solid var(--hairline); }
.faq-item .title { color: var(--ink); }
.faq-item p { margin-top: var(--s-sm); font-size: var(--t-body-sm); line-height: 1.7; color: var(--ink-soft); }

/* ---------- portfolio: case studies ---------- */

.case + .case { margin-top: var(--s-section); }
.case-head { display: grid; grid-template-columns: 1fr auto; gap: var(--s-lg) var(--s-xl); align-items: end; }
.case-head .headline { margin-top: 4px; }
.case-head .roles { margin-top: 4px; }
.case-visit { align-self: end; }
/* No measure cap here on purpose. The case blurb is meant to run to the same
   right edge as the screenshot frame directly beneath it, so it reads as one
   block rather than a narrow column floating over a wide image. .wrap already
   bounds it at 1120px. */
.case .passage { margin-top: var(--s-md); max-width: none; }

.frames { margin-top: var(--s-lg); display: grid; gap: var(--s-lg); }
.frames-pair { grid-template-columns: 1fr 1fr; }
.frames figure { margin: 0; }
.frame-label { margin-bottom: 10px; display: flex; align-items: center; gap: var(--s-sm); }
.frame-label .tick { width: 14px; height: 1px; background: var(--hairline-warm); flex: none; }
/* Same white mount and content lift as the homepage work cards, one step larger
   because these are the page's subject rather than a teaser. */
.frame {
  background: var(--card); padding: var(--work-card-pad);
  border-radius: var(--r-sm); box-shadow: var(--lift-content);
}
.frame img, .frame video {
  border-radius: var(--r-image); width: 100%; display: block;
  object-fit: cover; object-position: top center;
  background: var(--beige);
}
.frame-solo img { height: clamp(300px, 40vw, 460px); }
.frames-pair .frame img, .frames-pair .frame video { height: clamp(210px, 26vw, 330px); }
a.frame-link { display: block; text-decoration: none; }
a.frame-link .frame { transition: transform 220ms var(--ease); }
a.frame-link:hover .frame { transform: translateY(-3px); }

/* ---------- contact ---------- */

/* No .contact-grid any more. The form used to share the row with an aside holding
   Prefer email, Hours and Based in; that aside was cut on 2026-07-30 and the form
   now has the full 1000px to itself. The paired rows below are what keep it from
   reading as one long stack: two fields to a row across the full width puts the
   whole form on one screen. */
.form { display: flex; flex-direction: column; gap: var(--s-lg); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-md); }
.field { display: block; }
.field .flabel {
  display: flex; align-items: baseline; gap: var(--s-sm);
  font-size: var(--t-ui); font-weight: 500; color: var(--ink);
  margin-bottom: 7px;
}
.field .opt { font-size: 12.5px; font-weight: 400; color: var(--ink-mute); }
/* White is the content surface elsewhere on the site; a field is a control, and
   it needs to read as somewhere you can type. Hairline border, no shadow. */
.field input, .field textarea, .field select {
  width: 100%;
  font-family: var(--body); font-size: 15px; line-height: 1.6;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--hairline-warm);
  border-radius: var(--r-sm);
  padding: 12px 14px;
  transition: border-color 180ms var(--ease), box-shadow 180ms var(--ease);
}
.field textarea { min-height: 140px; resize: vertical; }
.field select {
  appearance: none; -webkit-appearance: none;
  padding-right: 38px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path d='M1 1l5 5 5-5' stroke='%236F6759' stroke-width='1.4' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat; background-position: right 14px center;
}
.field select[data-empty] { color: var(--ink-mute); }
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none;
  border-color: var(--raspberry);
  box-shadow: 0 0 0 3px rgba(184, 74, 112, .13);
}
.field .help { margin-top: 6px; font-size: 12.5px; line-height: 1.5; color: var(--ink-mute); }
.field .err { margin-top: 6px; font-size: 12.5px; line-height: 1.5; color: var(--raspberry-deep); }
.field-bad input, .field-bad textarea { border-color: var(--raspberry-deep); }
.honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

.form-send { display: flex; align-items: center; justify-content: space-between; gap: var(--s-lg); }
/* The 34ch cap here was sized for the old narrow form column. With the form on
   the full width there is around 780px beside the button, so the cap only served
   to break a one-line sentence across two lines. Flex shrinks it when the row
   really does run out, and under 560px the two stack anyway. */
.form-send .fineprint { font-size: 12.5px; line-height: 1.6; color: var(--ink-mute); }
.btn-primary[disabled] { opacity: .62; cursor: not-allowed; }
.form-error { font-size: var(--t-body-sm); color: var(--raspberry-deep); }

.sent .headline { margin-top: var(--s-md); }
.sent .lede { margin-top: var(--s-md); max-width: 54ch; }
.sent .link-quiet { margin-top: var(--s-lg); }

/* ---------- privacy and terms ---------- */

.doc-grid { display: grid; grid-template-columns: 190px 1fr; gap: 56px; align-items: start; }
.toc { position: sticky; top: var(--s-xl); }
.toc .toc-head { padding-bottom: var(--s-sm); border-bottom: 1px solid var(--hairline); }
.toc-list { margin-top: var(--s-sm); display: flex; flex-direction: column; gap: 2px; }
.toc-list a {
  display: flex; gap: 10px;
  font-size: var(--t-ui); color: var(--ink-mute); text-decoration: none;
  padding: 4px 0;
  transition: color 180ms var(--ease);
}
.toc-list a:hover { color: var(--ink); }
.toc-list .n { font-variant-numeric: tabular-nums; flex: none; }

.doc { max-width: var(--w-services); }
.doc-section + .doc-section { margin-top: var(--s-2xl); }
.doc-section { scroll-margin-top: var(--s-xl); }
/* The headline role held at the small end of its own clamp. Twelve sections at
   the full 31px reads as twelve shouted titles; 24px is the same role, quieter. */
.doc-section .headline { margin: 0; font-size: 24px; }
.doc-section p { margin-top: var(--s-md); font-size: var(--t-body-sm); line-height: 1.75; color: var(--ink-soft); max-width: 52ch; }
.doc-section ul { list-style: none; padding: 0; margin: var(--s-md) 0 0; max-width: 52ch; display: flex; flex-direction: column; gap: 10px; }
.doc-section li {
  display: flex; gap: 13px;
  font-size: var(--t-body-sm); line-height: 1.7; color: var(--ink-soft);
}
.doc-section li .dot { width: 4px; height: 4px; border-radius: 50%; background: var(--ink-mute); margin-top: 10px; flex: none; }
/* A tinted panel would be a second filled colour area, which the accent rule
   does not allow, so an aside is held between two hairlines and set in the
   display face instead. */
.doc-section .doc-note {
  margin-top: var(--s-lg);
  max-width: 52ch;
  padding: var(--s-md) 0;
  border-top: 1px solid var(--hairline-warm);
  border-bottom: 1px solid var(--hairline-warm);
  font-family: var(--display); font-style: italic;
  font-size: 17px; line-height: 1.55; color: var(--ink);
}
.doc-foot { margin-top: var(--s-2xl); padding-top: var(--s-lg); border-top: 1px solid var(--hairline); }
.doc-foot p { font-size: var(--t-body-sm); color: var(--ink-soft); }

/* ---------- the short close used on subpages ---------- */

/* The homepage close runs two columns because it has a paragraph to place. A
   subpage close is one line and one action, so it stays on a single row. */
.close-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s-lg) var(--s-xl); flex-wrap: wrap; }
.close-row .headline { max-width: 30ch; }

/* ---------- no-script fallback ---------- */

.noscript { max-width: 62ch; margin: 0 auto; padding: 64px var(--pad-side); }
.noscript h1 { font-size: 31px; font-weight: 500; }
.noscript p { margin-top: var(--s-md); }

/* ---------- subpage responsive ----------
   These sit at the end of the file on purpose. A media query adds no
   specificity, so a breakpoint rule for .case-head has to come after the base
   .case-head rule above or the base one simply wins. */

/* The shared subpage grid stacks earlier than the 820px everything else uses, and
   the reason is measured rather than preferred. The gap is 32px, so at 830px the
   offer's reading column lands at 378px, about 46 characters. Typography in
   DESIGN.md already flags that case: a narrow column at full body size leaves too
   few words to the line, which is why the process steps drop to Body Small.
   Stacking is the plainer answer than a second font size. 1024px matches where
   the hero and the About intro already stack, and at 1025px the column is still
   480px, about 58 characters, so nothing is cramped on the way there.

   Both selectors stack together because they are one grid. If only one stacked,
   the page would show a two-column opening above a single-column list or the
   reverse, which is the mismatch this whole change was meant to remove. */
@media (max-width: 1024px) {
  .offer { grid-template-columns: 1fr; gap: var(--s-lg); }
  .offer .body p { max-width: 52ch; }
  .section-two { grid-template-columns: 1fr; gap: var(--s-lg); }
  .section-two .section-head { max-width: 52ch; }
  .section-two .prose { max-width: 52ch; }
  .intro-grid { grid-template-columns: 1fr; gap: var(--s-xl); }
  /* Stacked, the intro is the single column it used to be and has to measure the
     same. The row gap goes to zero and the rule takes its own 24px margin back,
     otherwise the two add up and open a 56px hole under the display line. */
  .intro-open { grid-template-columns: 1fr; gap: 0; }
  .intro-open .measured { margin-top: var(--s-lg); }
  .intro-narrow .lede { max-width: 52ch; }
}

@media (max-width: 820px) {
  /* the legal contents list moves above the document */
  .doc-grid { grid-template-columns: 1fr; gap: var(--s-xl); }
  .toc { position: static; }
  .toc-list { flex-direction: row; flex-wrap: wrap; gap: var(--s-sm) var(--s-lg); }
  .toc-list a { padding: 0; }
  .case-head { grid-template-columns: 1fr; gap: var(--s-md); }
  .case-visit { justify-self: start; }
  .frames-pair { grid-template-columns: 1fr; }
  .frames-pair .frame img, .frames-pair .frame video { height: clamp(210px, 46vw, 300px); }
}

@media (max-width: 560px) {
  .field-row { grid-template-columns: 1fr; }
  .form-send { flex-direction: column; align-items: flex-start; gap: var(--s-md); }
}
