.wordmark {
  display: inline-block;
  line-height: 0; /* collapse to the image's own box, no inherited text line-height gap */
}

.wordmark img {
  height: auto;
}

.wordmark img {
  /* 16.7rem (~267px) — renders at ~28px tall, a typical header-logo height.
     The first pass here preserved the old stacked mark's ~54.8px rendered
     height, which read as too large/dominant once translated into a
     single-line horizontal lockup at the same cap-height. */
  width: 16.7rem;
}

.wordmark--footer img {
  /* ~152px — noticeably larger than the old 120px footer mark, but still
     under the header's own 176px, keeping the header as the primary mark. */
  width: 9.5rem;
}

/* HEADER WORDMARK — sized to visible ink, not the image box. The SVG's own
   artwork was checked first (getBBox against viewBox: 0-0.6px of padding on
   every edge, i.e. none worth compensating for) — height:auto/width:16.7rem
   above rendered the actual letters far larger than the 14px nav beside
   them. copenhagenbureau is set entirely in lowercase, so it has no cap-
   height band at all; per-letter getBBox on the source SVG's own paths
   pinned its baseline (~y70.3 of 91), x-height top (~y16.8) and ascender
   top (y0) precisely. Matching that x-height/ascender band to the nav's
   own measured x-height/ascender at 14px (canvas-measured against the
   real Inter font, not assumed from font-size) converged on ~13.2px from
   two independent bands, confirmed against a live screenshot next to
   "Contact". Scoped to .site-header specifically (higher specificity than
   the general rule above) so the footer's own 9.5rem instance, and the
   project drawer's separate .project-drawer__wordmark, are untouched. */
.site-header .wordmark img {
  width: auto;
  height: 17px; /* up from 15px — nudged slightly larger again on request */
}

.nav-link {
  font-family: var(--cb-font-sans);
  /* Regular, not medium — a refinement pass deliberately quietens Expertise/
     The Bureau (and the footer's own Explore column, which shares this same
     class) relative to Contact's own .cta--nav treatment below, which keeps
     a touch more weight. */
  font-weight: var(--cb-weight-regular);
  /* Small, not body size — reusing the design system's existing small-text
     token (14px, fixed) rather than inventing a one-off value, since it
     already sits exactly in the "14-15px, calmer and more elegant" range a
     later refinement pass asked for. The footer's own .site-footer .nav-link
     rule already set this same value independently before this change (see
     that rule's own comment) — it's now redundant there but left alone
     rather than removed, since a future footer-only tweak may need to
     diverge from the header again. */
  font-size: var(--cb-text-small-size);
  line-height: var(--cb-text-body-leading);
  /* Graphite, not Ink — quieter than Contact's own .cta--nav (still Ink),
     part of the same refinement pass's hierarchy: wordmark strongest,
     Contact next, Expertise/The Bureau/the language selector quietest.
     The footer's own .site-footer .nav-link rule overrides this back to
     Paper regardless (higher specificity), so this only ever visibly
     affects the header instance. */
  color: var(--cb-color-text-secondary);
  /* "What We Do" is a real <a href> (the other two are inert <span>s), so
     without this it alone picked up the browser's default link underline. */
  text-decoration: none;
}

.nav-link.is-placeholder {
  cursor: default;
}

/* Global design-consistency pass: was --cb-text-display-size (44-72px) at
   weight-light — the one page-title left on the old oversized display
   scale after every other public page's H1 (The Bureau, every Expertise
   capability) and every other H2 on THIS SAME page (.capabilities__heading,
   .selected-work__heading, .faq__heading, .contact__heading,
   .bureau-access-promo__heading — all --cb-text-page-heading-size already)
   had already moved to the smaller, approved page-heading register. That
   pass then went one step too far on this one element: the homepage hero
   is the site's single primary statement and reads too small next to its
   own supporting paragraph/CTA at the shared page-heading size. Restored
   to its own explicit, larger scale — a fluid clamp (not the shared
   --cb-text-page-heading-size token, and not shared with any other H1/H2
   on the site) landing at ~40px mobile / ~48px tablet / ~56px desktop,
   weight kept at medium and every other page's H1/H2 left untouched. */
.hero__heading {
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-medium);
  font-size: clamp(2.5rem, 2.1rem + 1.7vw, 3.5rem);
  line-height: 1.08;
  letter-spacing: var(--cb-text-page-heading-tracking);
}

/* Large-screen continuation (V-1, docs/FINAL_VISUAL_REVIEW.md) — the clamp
   above tops out at 56px by ~1318px viewport width and stays there through
   1920px+, so the site's single strongest statement loses presence on
   genuinely large displays while the rest of the homepage keeps growing.
   Kicks in only above the normal desktop tier (90rem/1440px, where the
   clamp above already reads 56px) and is itself a fluid clamp continuing
   smoothly to 66px by 1920px — 1440px and below are unaffected. Weight,
   line-height, letter-spacing and max-width (layout.css's 20ch) are
   untouched; size only. */
@media (min-width: 90rem) {
  .hero__heading {
    font-size: clamp(3.5rem, 1.625rem + 2.08vw, 4.125rem); /* 56–66px */
  }
}

/* Global design-consistency pass: .hero__supporting used to override to the
   larger subheading scale (19-20px) here — the one hero intro paragraph
   sitewide not reading at plain body size. Every other section's supporting
   copy on this same page (.capabilities__supporting, .selected-work__supporting,
   .faq__intro-text, .contact__body) inherits body typography with no
   override; removed here so this one now matches. layout.css still sets its
   margin-top/max-width/color. */

/* CTAs — one shared minimal editorial text-link treatment, not a filled box: plain
   ink text with a trailing arrow, an animated underline, and a nudge on the arrow.
   .cta--nav/.cta--hero/.cta--capabilities only ever adjust size or position for
   their context — the visual language itself lives entirely in .cta/.cta__label/
   .cta__arrow so every CTA reads the same way. Transparent padding + an equal
   negative margin gives a bigger tap target on all four sides without the element
   visually taking up more space or nudging surrounding layout (the two cancel out;
   only the hit-testing box grows). */
.cta {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-semibold);
  font-size: var(--cb-text-body-size);
  color: var(--cb-color-text);
  text-decoration: none;
  white-space: nowrap;
  background: none;
  border: none;
  padding: 0.5em 0.25em;
  margin: -0.5em -0.25em;
  cursor: pointer;
}

/* Only meaningful on a <button class="cta"> (an <a> can't be disabled) —
   e.g. the login page's submit CTA mid-request. Same restrained feedback
   as every other disabled control on the site (opacity + not-allowed
   cursor), not a new pattern. */
.cta:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* The underline is a background gradient sized 0%→100% wide, not border-bottom or
   text-decoration — a background-size transition doesn't affect line-height or
   push the arrow the way animating a border/outline would, so the label's own box
   never changes size while it animates. Scoped to the label only (not .cta__arrow)
   per brief: "a thin underline should animate beneath the text", not the arrow. */
.cta__label {
  background-image: linear-gradient(currentColor, currentColor);
  background-position: 0 100%;
  background-repeat: no-repeat;
  background-size: 0% 1px;
  transition: background-size 280ms var(--cb-ease-standard);
}

.cta__arrow {
  display: inline-block;
  transition: transform 280ms var(--cb-ease-standard);
}

/* :not(.is-placeholder) so the inert "Discover" span (no destination yet, see
   index.html) reads visually identical to a real CTA but never implies it's
   clickable — matching how .nav-link.is-placeholder already gets no hover
   treatment elsewhere. Real CTAs get the same feedback on keyboard focus as on
   hover; the browser's own focus-visible outline (base.css) still applies too. */
.cta:not(.is-placeholder):hover .cta__label,
.cta:not(.is-placeholder):focus-visible .cta__label {
  background-size: 100% 1px;
}

.cta:not(.is-placeholder):hover .cta__arrow,
.cta:not(.is-placeholder):focus-visible .cta__arrow {
  transform: translate(3px, -3px);
}

/* Global design-consistency pass: the underline/arrow transitions above had
   no reduced-motion carve-out (unlike .nav-burger__line, .nav-drawer__panel,
   .expertise-tabs__tab and every other animated control on the site, which
   all already zero their transition here). */
@media (prefers-reduced-motion: reduce) {
  .cta__label,
  .cta__arrow {
    transition: none;
  }
}

.cta--nav {
  /* Small, matching .nav-link's own updated size above — Contact reads at
     the same calmer scale as Expertise/The Bureau now; weight (below) is
     what still sets it apart, not size. */
  font-size: var(--cb-text-small-size);
  line-height: var(--cb-text-body-leading);
  /* Medium, not .cta's own semibold — Contact should still read slightly
     stronger than the now-regular-weight Expertise/The Bureau beside it,
     but full semibold felt heavier than the rest of this refined header. */
  font-weight: var(--cb-weight-medium);
}

.label {
  display: inline-block;
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-medium);
  font-size: var(--cb-text-label-size);
  line-height: var(--cb-text-label-leading);
  letter-spacing: var(--cb-text-label-tracking);
  text-transform: uppercase;
  color: var(--cb-color-text-secondary);
}

.capabilities__heading {
  /* No top margin: this used to sit below a "What we offer" eyebrow (space-4 gap to
     it), but that label was removed and the heading is now the column's first
     element — the leftover margin was just pushing it down from the top of the
     grid cell for no reason. */
  margin-top: 0;
  font-family: var(--cb-font-sans);
  /* Semibold (600), not the default page-heading 300 — a deliberate exception (see
     brand/copenhagen-bureau-brand-guidelines.md's Weights section) for a bolder,
     declarative register, matching a plain-statement-of-capability tone. */
  font-weight: var(--cb-weight-semibold);
  font-size: var(--cb-text-page-heading-size);
  line-height: var(--cb-text-page-heading-leading);
  letter-spacing: var(--cb-text-page-heading-tracking);
}

/* The <br> in index.html forces an even two-line split, but only once the two-column
   layout (same breakpoint as .capabilities__grid) narrows this column enough for that
   split to matter — below it the heading has the full row width and wraps naturally
   into a cleaner result on its own than the same fixed break point would produce. */
@media (max-width: 50rem) {
  .capabilities__heading br {
    display: none;
  }
}

.capabilities__supporting {
  /* Up from space-4 (24px) — sat too close under the heading given how much room the
     heading itself has around it. */
  margin-top: var(--cb-space-5);
  max-width: var(--cb-measure-body);
  color: var(--cb-color-text-secondary);
}

/* Same text-link treatment as the other .cta elements, but a <span>, not an <a> —
   genuinely inert (no tabindex) until the menu it will open exists, same pattern as
   the nav's "Our Work"/"The Bureau" placeholders; :not(.is-placeholder) above already
   excludes it from the hover/focus feedback a real link gets. margin-top overrides
   only the top of the shared .cta's negative margin, so the visible gap under the
   paragraph above is unaffected while the other three sides keep their transparent
   hit-area. Down from space-5 (32px) for the same reason as .hero__cta-group's own
   reduction — tuned for the old filled button, too detached for a slim text link. */
.cta--capabilities {
  margin-top: var(--cb-space-3);
  font-size: var(--cb-text-small-size);
  cursor: default;
}

.capability-ring__circle {
  /* A <button>, not a <span> — clickable now (no destination wired yet, see
     index.html's comment), so it needs the usual reset: browsers give buttons their
     own border/font/background by default. Positioning/rotation transform lives in
     layout.css; this element is otherwise just a plain, invisible hit-target the
     exact size of the resting (unscaled) circle — see .capability-ring__circle-visual
     for everything actually visible. Its own box size never changes on hover, per
     the "don't change the wrapper's size" requirement — only the inner element grows. */
  appearance: none;
  border: none;
  background: none;
  padding: 0;
  /* Its parent .capability-ring__item has pointer-events:none (see layout.css) so
     stacked items don't block each other — re-enable here since this is the one
     element that actually needs to receive hover/click/focus. */
  pointer-events: auto;
  cursor: pointer;
}

.capability-ring__circle-visual {
  /* The actual visible circle. A separate element from .capability-ring__circle
     specifically so hover-scale never shares a `transform` with orbit positioning:
     an earlier version folded scale into the SAME transform as the counter-rotation,
     on an element that also had a permanently-running CSS @keyframes animation on
     that property — meaning every hover change could only ever apply as an instant,
     un-eased cut (animations take priority over transitions on the same property, so
     no amount of transition/easing tuning could ever have smoothed it; the property
     simply wasn't available for a transition to act on). This element has no
     @keyframes, ever, so its own transition is never competing with anything. */
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  padding: var(--cb-space-2);
  text-align: center;
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-medium);
  font-size: var(--cb-text-small-size);
  line-height: 1.15;
  /* Default/inactive state colours — --capability-circle-bg/-text live on the
     ancestor .capability-ring (layout.css), specific hex values from the
     colour brief. No border, no shadow — nothing here ever sets either.
     Explicit background-color/color transitions (not `transition: all`)
     alongside the existing transform one, all on this one line so there's
     one property list to read, not several rules each declaring their own
     subset. */
  background: var(--capability-circle-bg);
  color: var(--capability-circle-text);
  transform: scale(1);
  transition:
    background-color 350ms var(--cb-ease-standard),
    color 350ms var(--cb-ease-standard),
    transform var(--cb-duration-base) var(--cb-ease-standard);
}

/* Only the actually-hovered/focused circle ever changes — every other circle
   (including while one IS active) just stays at its plain default styling
   above; there is no separate "dimmed" appearance any more, since inactive
   circles now already sit at the same beige this rule would otherwise dim
   them to. js/main.js still toggles .is-interacting on the ring for other
   bookkeeping, but no CSS reads that class any more. */
.capability-ring__circle:hover .capability-ring__circle-visual,
.capability-ring__circle:focus-visible .capability-ring__circle-visual {
  /* var(--capability-hover-scale) lives on the ancestor .capability-ring
     (layout.css) — the same value its width/max-width reserve extra clearance for,
     so the two can never drift out of sync. Each circle only ever grows on its own
     hover/focus and shrinks back on leave/blur; nothing else about the ring reacts. */
  background: var(--capability-circle-bg-active);
  color: var(--capability-circle-text-active);
  transform: scale(var(--capability-hover-scale));
}

.selected-work__heading {
  /* Same page-heading scale/weight as .capabilities__heading — a declarative
     register shared across both sections, not a one-off size. */
  margin: 0;
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-semibold);
  font-size: var(--cb-text-page-heading-size);
  line-height: var(--cb-text-page-heading-leading);
  letter-spacing: var(--cb-text-page-heading-tracking);
}

.selected-work__intro .label {
  margin-bottom: var(--cb-space-3);
}

.selected-work__supporting {
  margin-top: var(--cb-space-4);
  color: var(--cb-color-text-secondary);
}

.process__intro .label {
  margin-bottom: var(--cb-space-3);
}

/* The changing stage title/paragraph — aria-hidden decorative stack
   (index.html); the real content lives in the visually-hidden <ol> beside
   it. No step number here at all this pass (brief: "remove 01/02/03/04
   from the left side... only the title + paragraph change"). Values are
   .capabilities__heading/.capabilities__supporting's EXACT values (not
   reinvented) — brief: "typography must match the other sections exactly"
   — margin-top corrected to space-5 here to genuinely match
   .capabilities__supporting's own (a real mismatch from an earlier pass,
   caught this time re-reading that rule directly rather than assumed
   still-correct). */
.process__stage-title {
  margin: 0;
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-semibold);
  font-size: var(--cb-text-page-heading-size);
  line-height: var(--cb-text-page-heading-leading);
  letter-spacing: var(--cb-text-page-heading-tracking);
}

.process__stage-summary {
  margin-top: var(--cb-space-5);
  max-width: var(--cb-measure-body);
  color: var(--cb-color-text-secondary);
}

/* space-2, not the previous space-5 — brief: "move the CTA closer to the
   supporting copy... reduce the vertical gap so the CTA feels attached to
   the text rather than floating too low." */
.process__cta {
  margin-top: var(--cb-space-2);
}

/* The "STEP N" label inside the ring — a quiet editorial label, not a
   heading: uppercase, approximately 12-14px, letter-spacing approximately
   0.14-0.18em, left-aligned, secondary to the detailed paragraph below it
   — but "slightly stronger" than a Medium weight read as (brief), hence
   Semibold rather than Medium here. Reuses the SAME small/label tokens the
   sitewide eyebrow ("HOW THE BUREAU WORKS", .label) already uses —
   --cb-text-small-size is a fixed 14px (top of the target range) and
   --cb-text-label-tracking is already exactly 0.16em — rather than
   inventing new values, per this file's own convention of reusing
   existing tokens where they already match. text-transform:uppercase (not
   literal caps in the HTML) matches how .label does it elsewhere on this
   page. One size at every breakpoint now (no separate mobile-first/
   desktop split like the previous, much bigger heading needed) — 14px
   comfortably clears the innermost ring even at the tightest tablet-dip
   circle. space-4 (24px) margin-bottom sits at the bottom edge of the
   brief's "approximately 24-28px" target. */
.process__ring-statement {
  margin: 0 0 var(--cb-space-4);
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-semibold);
  font-size: var(--cb-text-small-size);
  line-height: var(--cb-text-label-leading);
  letter-spacing: var(--cb-text-label-tracking);
  text-transform: uppercase;
  color: var(--cb-color-text-secondary);
}

/* Supporting paragraph inside the ring — mobile-first: this base rule is
   the COMPACT size, correct below the ≥64rem override further down. A
   small ring (full-bleed mobile width, or the ~317px-wide tablet dip
   between 50.0625rem and 64rem) simply can't hold the brief's desktop
   type target without the text block's corners reaching past the
   innermost dotted ring; confirmed by measuring the text block's own
   diagonal against that ring's radius, not by eye. */
.process__ring-paragraph {
  margin: 0;
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-regular);
  font-size: 0.6875rem;
  line-height: 1.25;
  color: var(--cb-color-text-secondary);
}

/* Intermediate desktop tier (1024-1439px) — the ring here (layout.css:
   ~515-780px) has noticeably less room than the ≥90rem tier below, and
   this round's left-aligned text (every line now starts flush at the
   column's own left edge, rather than centred lines leaving slack at the
   corners) measurably tightened the text block's own corner clearance
   against the innermost ring here — down to a bare 3px at 1024px with the
   ≥90rem tier's larger paragraph size applied this broadly. Kept at the
   sitewide body token (16-17px) instead, which is what this tier used
   safely before — re-verified by measurement, comfortably positive again. */
/* FINAL REVIEW (F-3): tablet (48rem–64rem, i.e. 768–1023px) — .process__ring
   is still in its full-bleed, width:100vw mode here (layout.css), so the
   ring is actually a ~768-820px circle at this range, BIGGER than the
   780px-capped "intermediate desktop" ring just below (the ≥64rem tier
   further down). The base rule above was sized for the smallest case in its
   own "mobile-first" range (a ~375-430px phone-width ring); at genuine
   tablet width that left the STEP label/paragraph reading as miniature
   inside a much larger graphic. Real room exists here for a size at least
   as generous as the intermediate desktop tier below — confirmed by the
   ring being no smaller. Graphic itself is untouched; only the text grows. */
@media (min-width: 48rem) and (max-width: 63.9375rem) {
  .process__ring-statement {
    font-size: 1rem;
  }

  .process__ring-paragraph {
    font-size: clamp(1rem, 2vw, 1.125rem);
    line-height: 1.5;
    letter-spacing: var(--cb-text-body-tracking);
  }
}

@media (min-width: 64rem) {
  .process__ring-paragraph {
    font-size: var(--cb-text-body-size);
    line-height: 1.55;
    letter-spacing: var(--cb-text-body-tracking);
  }
}

/* Same intermediate tier, tightened one step further: .process__ring-
   stage's align-self:start fix (elsewhere in layout.css) locks every
   stage's content to the same top-left point of the shared cell, so a
   stage shorter than the tallest variant no longer gets the small amount
   of top padding align-self:centre used to give it "for free". That
   tipped this tier's own worst-case corner distance (the "Diagnose"
   stage) just barely past the innermost ring's radius (measured: -0.2px).
   line-height trimmed slightly (1.55 -> 1.45) restores real, positive
   clearance — still reads as normal body copy at this size, and the
   ≥90rem tier above isn't affected. */
@media (min-width: 64rem) and (max-width: 89.9375rem) {
  .process__ring-paragraph {
    line-height: 1.45;
  }
}

/* Large-desktop tier — ≥90rem is where the ring (780-970px) has real
   spare room (measured: 39-121px of clearance even at the brief's own
   "approximately 17-18px" target), so this is the one tier that actually
   gets that slightly larger size. */
@media (min-width: 90rem) {
  .process__ring-paragraph {
    font-size: clamp(1.0625rem, 1rem + 0.2vw, 1.125rem);
  }
}

/* FAQ eyebrow-to-heading gap — same scoped-label-margin pattern already
   used by .selected-work__intro/.process__intro's own ".label" overrides
   in this file, not a new one. */
/* space-3 (16px), was space-4 (24px) — part of this section's compaction
   pass; still a real, visible gap, just tighter. */
.faq__intro .label {
  margin-bottom: var(--cb-space-3);
}

/* Same page-heading scale/weight already used for .capabilities__heading,
   .selected-work__heading and .contact__heading below — this section
   joins that shared register rather than introducing a new heading size,
   per brief ("do not create a new oversized heading style"). */
.faq__heading {
  margin: 0;
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-semibold);
  font-size: var(--cb-text-page-heading-size);
  line-height: var(--cb-text-page-heading-leading);
  letter-spacing: var(--cb-text-page-heading-tracking);
}

/* Font-family/weight/size/line-height deliberately NOT set here — inherits
   from body (base.css), the exact same way .selected-work__supporting and
   .contact__body get their own supporting-copy styling (neither declares
   them either) — brief: "match the supporting copy used in the Selected
   Work or contact-form section". */
/* space-4 (24px), was space-5 (32px) — same compaction pass as
   .faq__intro .label's own reduced margin above. */
.faq__intro-text {
  margin-top: var(--cb-space-4);
  color: var(--cb-color-text-secondary);
}

/* Question — semibold ink, one step down from the page-heading register
   above (this is a list of practical questions, not a second heading
   tier); body's existing 16-17px size, not a bespoke one. */
.faq__question {
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-semibold);
  font-size: var(--cb-text-body-size);
  color: var(--cb-color-ink);
}

/* Answer — same body size as the question, differentiated by weight/colour
   rather than size, so the accordion reads as one consistent, editorial
   reading size rather than a smaller "fine print" tier. */
.faq__answer {
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-regular);
  font-size: var(--cb-text-body-size);
  line-height: var(--cb-text-body-leading);
  color: var(--cb-color-text-secondary);
}

.contact__heading {
  /* Same page-heading scale/weight already used for .capabilities__heading
     and .selected-work__heading — this section joins that shared register
     rather than introducing a fourth heading size. */
  margin: var(--cb-space-3) 0 0;
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-semibold);
  font-size: var(--cb-text-page-heading-size);
  line-height: var(--cb-text-page-heading-leading);
  letter-spacing: var(--cb-text-page-heading-tracking);
}

.contact__body {
  margin-top: var(--cb-space-4);
  color: var(--cb-color-text-secondary);
}

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

.form-field label {
  /* 16px/600/1.2 is an explicit, literal spec from the brief — no existing
     type-scale row combines this exact size+weight+line-height for a form
     label, so this doesn't reuse one the way most typography on this site
     does. margin-bottom isn't set here: .form-field's own flex `gap`
     (--cb-space-2, 8px) already produces the requested label-to-field gap,
     so adding a margin on top would double it. */
  font-family: var(--cb-font-sans);
  font-weight: var(--cb-weight-semibold);
  font-size: 1rem;
  line-height: 1.2;
  color: var(--cb-color-text);
}

/* Pale neutral-grey fill + a subtle border, not the previous transparent-
   Paper/ink-tinted-border treatment — that read as too heavy/box-like once
   built (brief: "the permanent dark outlines dominate the design"). Both
   colours are still derived from the existing Paper/Ink tokens via
   color-mix, matching the brief's own suggested #F4F4F2/#D2D1CD closely,
   rather than hardcoding two brand-new raw hex constants. border-radius
   uses --cb-radius-input (14px) — a deliberate, explicit exception to this
   system's documented 4px/8px-CTA radius scale (tokens.css) — a literal
   brief spec for this one component, not a wider system change. */
.form-field input,
.form-field select,
.form-field textarea {
  width: 100%;
  font-family: var(--cb-font-sans);
  font-size: 1rem;
  color: var(--cb-color-text);
  background: color-mix(in srgb, var(--cb-color-paper) 97%, var(--cb-color-ink) 3%);
  border: 1px solid color-mix(in srgb, var(--cb-color-paper) 82%, var(--cb-color-ink) 18%);
  border-radius: var(--cb-radius-input);
  transition: border-color 200ms var(--cb-ease-standard), background-color 200ms var(--cb-ease-standard);
}

/* Height, not vertical padding, sizes these to 48px — box-sizing:border-box
   (base.css) means border+padding+content all count inside that height, and
   0 vertical padding lets the browser's own native baseline centring place
   the value (and, for <select>, its arrow) in the middle, per brief. Only
   input/select get a fixed height; textarea stays intrinsically sized via
   min-height below. */
.form-field input,
.form-field select {
  height: 3rem;
  padding-inline: var(--cb-space-3);
}

.form-field textarea {
  /* ~120px per brief, down from the previous ~144px. */
  min-height: 7.5rem;
  padding: 0.875rem 1rem;
  resize: vertical;
}

.form-field input:hover,
.form-field select:hover,
.form-field textarea:hover {
  /* Strengthened from the 18% default, still well short of full Ink — "do
     not change the field background dramatically" leaves background alone
     here, only the border shifts. */
  border-color: color-mix(in srgb, var(--cb-color-paper) 68%, var(--cb-color-ink) 32%);
}

/* Validation error state (js/main.js's initContactForm toggles this class on
   the .form-field wrapper). #D92D20 is a literal, one-off brief spec, not a
   sitewide token — this is the only place on the page a red appears at all.
   border-color only, not the `border` shorthand, so width/style are
   untouched; no background change ("no red background"). Placed before the
   :focus-visible rule below, at equal specificity, so focus still visually
   wins while a field is both invalid and focused — "preserve the existing
   visible keyboard-focus styling" stays true even in that combined state. */
.form-field.is-invalid input,
.form-field.is-invalid select,
.form-field.is-invalid textarea {
  border-color: #d92d20;
}

/* Full Ink border + a soft Ink-tint ring, not the previous full solid-Ink
   sitewide outline (base.css's input:focus-visible) — that rule still
   applies globally, but this selector is more specific (.form-field input
   vs input) and wins for these fields specifically, matching the brief's
   own "do not use a thick black permanent outline" direction for form
   fields without weakening focus visibility anywhere else on the site. */
.form-field input:focus-visible,
.form-field select:focus-visible,
.form-field textarea:focus-visible {
  border-color: var(--cb-color-ink);
  outline: 2px solid color-mix(in srgb, var(--cb-color-ink) 18%, transparent);
  outline-offset: 2px;
}

/* Error message — a normal flex child of .form-field (column, gap: 8px), so
   the existing gap alone produces the required "8px beneath the field" once
   this is unhidden; nothing renders (and the gap contributes no space) while
   [hidden] keeps it display:none, so a valid field's layout is completely
   unaffected. js/main.js sets textContent and toggles [hidden] together. */
.form-field__error {
  margin: 0;
  color: #d92d20;
  font-size: 14px;
  line-height: 1.4;
}

/* U-14 — concise password-policy guidance, shown only where a NEW
   password is being set (activation, reset). Same size/rhythm as
   .form-field__error but neutral secondary colour, not a warning. */
.form-field__hint {
  margin: 0;
  color: var(--cb-color-text-secondary);
  font-size: 14px;
  line-height: 1.4;
}

.contact__privacy {
  margin-top: var(--cb-space-5);
  max-width: var(--cb-measure-body);
  font-size: var(--cb-text-small-size);
  color: var(--cb-color-text-secondary);
}

.contact__privacy a {
  color: var(--cb-color-text-secondary);
}

.contact__submit-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cb-space-4);
  margin-top: var(--cb-space-4);
  /* Reserves the success icon's own height (42px) up front — .cta's
     negative margin (its own "bigger tap target" trick) makes the Submit
     button's real visual height taller than its actual contribution to
     this row's flow height, so without this the row's own box grows once
     the button is replaced by the icon, visibly nudging the footer down.
     Centred via align-items above either way, so the default/sending
     button (visually ~37px) isn't cramped inside this. */
  min-height: 42px;
}

/* Submit control — sending/success feedback for the standard enquiry form
   (homepage + contact.html's simple-form route; js/enquiry-form.js's
   wireEnquiryForm). .contact__submit-row itself, the fields, validation and
   privacy copy are all unchanged — only what happens beside/inside this one
   button on submit. */
.contact__submit {
  transition: opacity 150ms var(--cb-ease-standard), transform 150ms var(--cb-ease-standard);
}

.contact__submit.is-hiding {
  opacity: 0;
  transform: scale(0.96);
}

/* .contact__submit[hidden] (class + attribute selector) — .cta's own
   unconditional display:inline-flex rule above always overrides [hidden]'s
   own UA-default display:none at equal specificity; the extra attribute
   selector here gives this rule strictly higher specificity so it wins
   regardless of source order. Same gotcha this project has hit before with
   other toggled elements sharing a base component class. */
.contact__submit[hidden] {
  display: none;
}

.contact__submit:disabled {
  cursor: not-allowed;
}

/* Restrained three-dot "Sending…" pulse — brief: "a restrained three-dot
   pulse after the word... do not use a spinner, progress bar or loading
   overlay". Hidden by default; only shown while .contact__submit carries
   .is-sending (js/enquiry-form.js). */
.contact__submit-dots {
  display: none;
  align-items: center;
  gap: 3px;
  margin-left: 2px;
}

.contact__submit.is-sending .contact__submit-dots {
  display: inline-flex;
}

.contact__submit-dots span {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.3;
  animation: contact-dot-pulse 1.1s ease-in-out infinite;
}

.contact__submit-dots span:nth-child(2) {
  animation-delay: 0.15s;
}

.contact__submit-dots span:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes contact-dot-pulse {
  0%,
  80%,
  100% {
    opacity: 0.3;
  }
  40% {
    opacity: 1;
  }
}

/* Icon + status text, side by side — brief: "approximately 16-20px between
   the circle and message", "vertically aligned with the success circle". */
.contact__feedback {
  display: inline-flex;
  align-items: center;
  gap: 18px;
}

.contact__success-icon {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  opacity: 0;
  transition: opacity 300ms var(--cb-ease-standard) 100ms;
}

.contact__success-icon[hidden] {
  display: none;
}

.contact__feedback.is-success .contact__success-icon {
  opacity: 1;
}

/* Stroke-draw — dasharray comfortably longer than the check path's own
   ~26px length (no visual effect from the small excess; a shorter value
   would clip the stroke). Starts 120ms after the circle itself begins
   fading in, so the tick reads as drawn "inside" an already-visible
   circle rather than arriving with it. */
.contact__success-check {
  stroke-dasharray: 28;
  stroke-dashoffset: 28;
  transition: stroke-dashoffset 200ms var(--cb-ease-standard) 220ms;
}

.contact__feedback.is-success .contact__success-check {
  stroke-dashoffset: 0;
}

.contact__status {
  margin: 0;
  font-size: var(--cb-text-body-size);
  color: var(--cb-color-text-secondary);
  outline: none;
}

/* Two-step "armed then triggered" entrance (js/enquiry-form.js sets
   .is-entering, then swaps it for .is-visible one frame later) — only used
   for the final success message; the earlier "Sending…" text is set with
   neither class and simply appears without an entrance animation of its
   own. .is-entering carries no transition of its own so the very first
   frame renders instantly at the offset/invisible starting point, with
   nothing to animate from. */
.contact__status.is-entering {
  opacity: 0;
  transform: translateX(-8px);
}

.contact__status.is-visible {
  opacity: 1;
  transform: translateX(0);
  transition: opacity 300ms var(--cb-ease-standard) 100ms, transform 300ms var(--cb-ease-standard) 100ms;
}

/* Existing red validation colour (matches .form-field__error) — brief:
   "use the existing red validation colour". Only shown if the TEMPORARY
   submission promise in js/enquiry-form.js ever rejects. */
.contact__error {
  margin: 0;
  color: #d92d20;
  font-size: var(--cb-text-small-size);
  line-height: 1.4;
}

@media (prefers-reduced-motion: reduce) {
  .contact__submit,
  .contact__success-icon,
  .contact__success-check,
  .contact__status.is-visible {
    transition-duration: 1ms;
    transition-delay: 0ms;
  }

  .contact__status.is-entering,
  .contact__status.is-visible {
    transform: none;
  }

  .contact__submit-dots span {
    animation: none;
    opacity: 0.6;
  }
}
