@charset "utf-8";

/* ==========================================================================
   The Workshop HQ - site stylesheet
   --------------------------------------------------------------------------
   Load order matters:
     1. /assets/css/styles.css        (this file - everything site wide)
     2. /assets/css/availability.css  (the reusable availability module)

   This file owns: reset, tokens, typography, layout, header, nav drawer,
   hero, sections, buttons, cards, gallery, FAQ, forms, footer, motion,
   focus styling and print. It must never restyle .office-card, .status or
   .availability-summary - those belong to the module stylesheet.

   Mobile first. Checked at 375px, 768px and 1280px.
   British English. No em dashes anywhere.
   ========================================================================== */

/* --------------------------------------------------------------------------
   0. Webfonts - self hosted, from the official brand pack
   The Workshop Brand Guidelines 2026 name two faces: Brother 1816 Medium for
   headings and titles, Open Sauce Light for subtitles and paragraphs. Both are
   served from /assets/fonts so no third party sees a visitor request, and both
   are preloaded from the page head.

   Licences differ and it matters:
     Brother 1816  commercial, TipoType (Ignacio Corbo, Fernando Diaz). The
                   .woff2 came from the client's brand pack and is shipped
                   byte for byte, unmodified. A TipoType DESKTOP licence does
                   not cover web use. See docs/DECISIONS.md - this is an open
                   launch blocker, not a settled question.
     Open Sauce    SIL Open Font License 1.1, free to self host. Subset to
                   latin and converted to woff2 from the upstream TTFs. The
                   licence ships alongside at /assets/fonts/OFL-open-sauce-sans.txt
                   because OFL 1.1 requires it to travel with the font.

   Only the weights the site actually uses are declared. Adding a weight means
   adding a file, so check before you reach for one.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Brother 1816";
  src: url("/assets/fonts/brother-1816-medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Open Sauce Sans";
  src: url("/assets/fonts/open-sauce-sans-light.woff2") format("woff2");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Open Sauce Sans";
  src: url("/assets/fonts/open-sauce-sans-regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Open Sauce Sans";
  src: url("/assets/fonts/open-sauce-sans-semibold.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Open Sauce Sans";
  src: url("/assets/fonts/open-sauce-sans-bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   1. Tokens
   Palette sampled from the client's own photographs: black painted beams and
   Crittall frames, red brick, brass tap and plaque, off white walls, oak.
   -------------------------------------------------------------------------- */

:root {
  /* Core palette - pinned by the build contract, do not drift */
  --ink: #191713;
  --ink-soft: #3d3931;
  --muted: #6f6a60;
  --paper: #f5f2ec;
  --paper-2: #fbfaf8;
  --line: #ded8cc;
  --brick: #9c4f33;
  /* Sage - Painswick #AFB0A8, the third brand colour (Brand Guidelines 2026,
     page 13: White / Painswick / Black). The client's second-round amendments
     (24 Aug 2026) name it for the footer and the buttons: "sage, not yellow
     or red". Painswick itself is too light to carry text on a light ground,
     so two derived steps exist: --sage-strong for 3:1 UI marks on the light
     grounds, --sage-deep for 4.5:1 text and icons there. All three verified
     by scripts/check-contrast.mjs.
     --brick survives for ERROR SEMANTICS ONLY (field errors, invalid
     borders, the error summary); it is no longer a decorative accent. */
  --sage: #afb0a8;
  --sage-strong: #7d8073;
  --sage-deep: #5d6156;
  /* Bright step (27 Aug 2026): a greener lift for hover fills only.
     Fill-with-ink-label only - at 1.87:1 against paper it can NEVER carry
     text on the light grounds. It no longer shares a hue with the band:
     the client asked (9 Sep 2026) for the homepage band to be the same
     Painswick as the buttons, so --sage-band now equals --sage and is
     kept as its own token only so the band can move again on its own. */
  --sage-bright: #aab98f;
  --sage-band: #afb0a8;
  --ok: #2f5d43;
  --warn: #6a550f;
  --let: #4a463e;
  --radius: 4px;
  --radius-lg: 8px;
  --maxw: 1180px;
  --gutter: clamp(1.25rem, 5vw, 3.5rem);
  --step: clamp(3.5rem, 8vw, 7rem);

  /* Derived surfaces - built from the pinned palette, not new brand colours */
  --shell: #ece6da;
  --shell-2: #e3dbcb;
  --line-strong: #c9c1b1;
  /* Form control boundaries only. WCAG 2.2 SC 1.4.11 wants 3:1 for the edge
     of a control, and a field sits on --paper-2 inside a --paper-2 card, so
     its border is the only thing locating it: --line-strong is 1.79 there and
     fails. A separate token keeps every decorative hairline delicate rather
     than darkening the whole hairline family. 3.61 on paper-2.
     Verified by scripts/check-contrast.mjs. */
  --line-control: #8a8375;
  /* Small text on shell: --muted is 4.32:1 there (AA fail). This clears 4.5:1
     on shell without darkening the pinned --muted used on paper. */
  --muted-strong: #5e5a52;
  --ink-70: rgba(25, 23, 19, 0.7);
  --ink-12: rgba(25, 23, 19, 0.12);
  --paper-16: rgba(245, 242, 236, 0.16);

  /* Status tints consumed by availability.css, which has its own fallbacks */
  --ok-tint: #e9f0ea;
  --warn-tint: #f7efdc;
  --let-tint: #eceeeb;
  --unknown-tint: #f2efe8;

  /* Type - the official brand faces (Brand Guidelines 2026, pages 10 to 12).
     Fallbacks are chosen for skeleton, not availability: Brother 1816 is a
     geometric sans, so it falls back to other geometrics before a grotesque;
     Open Sauce Sans is a neo grotesque and falls back accordingly. */
  --font-display: "Brother 1816", "Avenir Next", Avenir, "Century Gothic",
                  "Helvetica Neue", Arial, sans-serif;
  --font-body: "Open Sauce Sans", "Helvetica Neue", Arial, system-ui, sans-serif;
  /* The guidelines specify Open Sauce LIGHT "for subtitles and paragraphs".
     Split, deliberately: Light (300) goes on the short intro paragraphs
     (.lead, .hero-lead) where it is 17 to 19px on --ink-soft over --paper, so
     roughly 9:1 - a light stem is safe and genuinely more elegant there.
     Running body copy stays at 400. A 300 stem at 16px over long measures
     thins out on low-DPI Windows without subpixel smoothing, and that costs
     more legibility than it buys refinement. Documented deviation, see
     docs/DECISIONS.md. */
  --fw-lead: 300;

  --fs-xs: 0.75rem;
  --fs-sm: clamp(0.8125rem, 0.79rem + 0.12vw, 0.875rem);
  --fs-base: clamp(1rem, 0.97rem + 0.16vw, 1.0625rem);
  --fs-md: clamp(1.0625rem, 1rem + 0.3vw, 1.1875rem);
  --fs-lg: clamp(1.25rem, 1.15rem + 0.5vw, 1.5rem);
  --fs-xl: clamp(1.5rem, 1.3rem + 0.9vw, 2.125rem);
  --fs-2xl: clamp(1.875rem, 1.5rem + 1.7vw, 3rem);
  --fs-3xl: clamp(2.375rem, 1.8rem + 2.6vw, 4rem);

  --lh-tight: 1.06;
  --lh-snug: 1.2;
  --lh-body: 1.6;

  --tracking-label: 0.1em;

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

  /* Chrome */
  --header-h: 4.75rem;
  --shadow-sm: 0 1px 2px rgba(25, 23, 19, 0.06);
  --shadow-md: 0 12px 30px -18px rgba(25, 23, 19, 0.45);
  --shadow-lg: 0 28px 60px -32px rgba(25, 23, 19, 0.55);
  --ease: cubic-bezier(0.2, 0.6, 0.2, 1);
  --dur-fast: 140ms;
  --dur: 240ms;
  --dur-slow: 620ms;
}

@media (min-width: 60em) {
  :root {
    --header-h: 6rem;
  }
}

/* --------------------------------------------------------------------------
   2. Modern reset
   -------------------------------------------------------------------------- */

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

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

body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd,
fieldset {
  margin: 0;
}

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

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  color: var(--ink);
  background: var(--paper);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

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

img {
  height: auto;
  border: 0;
}

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

button {
  background: none;
  border: 0;
}

fieldset {
  padding: 0;
  border: 0;
}

legend {
  padding: 0;
}

table {
  border-collapse: collapse;
}

hr {
  height: 0;
  margin: var(--space-6) 0;
  border: 0;
  border-top: 1px solid var(--line);
}

[hidden] {
  display: none !important;
}

:target {
  scroll-margin-top: calc(var(--header-h) + var(--space-5));
}

/* --------------------------------------------------------------------------
   3. Base typography
   Display face is Brother 1816 Medium, body is Open Sauce Sans. These are the
   client's own brand faces, specified in The Workshop Brand Guidelines 2026
   (pages 10 to 12), so there is nothing to choose here any more - the earlier
   slab-against-grotesque reasoning was a stand-in for a brand that had not
   reached us yet, and the three-way type chooser is now a record of that
   process rather than a live question.

   Both faces are sans, so the heading-to-body contrast is carried by skeleton
   and weight rather than by serif against sans: Brother is geometric (circular
   O, single-storey shapes, high crossbars) at 500, Open Sauce is a neo
   grotesque at 400. Labels, eyebrows, nav, buttons and badges all take the same
   stencilled workshop label texture: uppercase body face at a small size with
   generous tracking, which is how the guidelines set their own labels.
   -------------------------------------------------------------------------- */

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  /* 500 is Brother 1816 Medium, the only display weight shipped and the one the
     guidelines name. Requesting 400 here would still land on the 500 face by
     nearest-match, but only by accident - be explicit. */
  font-weight: 500;
  font-optical-sizing: auto;
  line-height: var(--lh-snug);
  /* Tracking stays at 0 here. The old -0.01em on this shared rule also landed on
     h4 at 17-19px, where tight setting is simply wrong: weight should fall as
     size rises, not tracking. Only h1 earns a nudge, below, because there the
     size does the optical work. */
  letter-spacing: 0;
  font-synthesis: none;
  text-wrap: balance;
}

h1 {
  font-size: var(--fs-3xl);
  line-height: var(--lh-tight);
  letter-spacing: -0.005em;
}

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

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

h4 {
  font-size: var(--fs-md);
  font-family: var(--font-body);
  font-weight: 700;
}

p {
  text-wrap: pretty;
}

/* Honest rag on every multi-line prose block. Headings already use balance. */
.lead,
.hero-lead,
.faq-answer p,
.card-text,
.gallery-caption,
.field-hint,
.note {
  text-wrap: pretty;
}

strong,
b {
  font-weight: 700;
}

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

a {
  color: inherit;
  text-decoration-color: var(--sage-deep);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
  transition: color var(--dur-fast) var(--ease);
}

a:hover {
  color: var(--sage-deep);
}

.text-link {
  color: var(--sage-deep);
  font-weight: 500;
  text-decoration: underline;
  text-decoration-color: currentColor;
  text-decoration-thickness: 1px;
}

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

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink);
  border-bottom: 1px solid var(--sage);
  padding-bottom: 0.35em;
  line-height: 1.4;
}

.link-arrow::after {
  content: "\2192";
  transition: transform var(--dur) var(--ease);
}

.link-arrow:hover {
  color: var(--sage-deep);
}

.link-arrow:hover::after {
  transform: translateX(0.25em);
}

.eyebrow {
  margin: 0 0 var(--space-3);
  color: var(--muted);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.4;
}

.lead {
  font-size: var(--fs-md);
  font-weight: var(--fw-lead);
  color: var(--ink-soft);
  max-width: 46ch;
}

.prose > * + * {
  margin-top: var(--space-4);
}

.prose p,
.prose li {
  max-width: 68ch;
  color: var(--ink-soft);
}

.prose h2,
.prose h3 {
  margin-top: var(--space-6);
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   4. Utilities
   -------------------------------------------------------------------------- */

.skip-link {
  position: absolute;
  z-index: 100;
  top: var(--space-2);
  left: var(--space-2);
  padding: 0.75rem 1.1rem;
  color: var(--paper);
  background: var(--ink);
  border-radius: var(--radius);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-decoration: none;
  transform: translateY(-160%);
  transition: transform var(--dur) var(--ease);
}

.skip-link:focus-visible,
.skip-link:focus {
  color: var(--paper);
  transform: translateY(0);
}

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

.stack > * + * {
  margin-top: var(--space-4);
}

.stack-lg > * + * {
  margin-top: var(--space-6);
}

.measure {
  max-width: 62ch;
}

/* Text for assistive technology only, e.g. "(opens in a new tab)" on the
   Google Maps links. The clip pattern keeps it in the accessibility tree
   without a visible or layout footprint. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

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

/* --------------------------------------------------------------------------
   5. Section rhythm
   -------------------------------------------------------------------------- */

.site-main {
  display: block;
  flex: 1 0 auto;
}

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

.section-tight {
  /* Half-interval of --step (true beat), not an arbitrary 0.55. */
  padding-block: calc(var(--step) * 0.5);
}

.section-alt {
  background: var(--shell);
}

/* Shell ground darkens perceived contrast. Rebind --muted for descendants
   (including the availability module's var(--muted, …) fallbacks) and lift
   eyebrows / captions to ink-soft. Hairlines step up to --line-strong so they
   do not vanish against the shell. Core :root --muted stays pinned for paper. */
.section-alt,
.page-head,
.callout {
  --muted: var(--muted-strong);
}

.section-alt .eyebrow,
.section-alt .gallery-caption,
.page-head .eyebrow {
  color: var(--ink-soft);
}

.section-alt .rule {
  background: var(--line-strong);
}

.section-alt .card,
.section-alt .gallery-group-title {
  border-color: var(--line-strong);
}

.section-paper {
  background: var(--paper-2);
}

.section-dark {
  color: var(--paper);
  background: var(--ink);
}

.section-dark h1,
.section-dark h2,
.section-dark h3 {
  color: var(--paper);
}

.section-dark .eyebrow {
  color: var(--sage);
}

.section-dark .lead,
.section-dark .prose p,
.section-dark .prose li {
  color: #ddd7cb;
}

.section-dark a {
  color: var(--paper);
  text-decoration-color: var(--sage);
}

.section-dark a:hover {
  color: var(--sage);
}

.section-dark .rule {
  background: var(--paper-16);
}

/* Two columns from 60em: eyebrow and title left, the supporting line right,
   bottom-aligned to the title. Use the split only when the right-hand line
   is a real supporting paragraph (a two-line note, or a lead that sits over
   a form column). A one-line lead floating in a half-width column reads as
   a leftover, so those heads take .section-head-stack, which keeps eyebrow,
   title and lead on one left edge with whatever grid follows. */
.section-head {
  display: grid;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}

.section-head .section-title {
  margin: 0;
}

.section-head .lead,
.section-head .note {
  margin: 0;
}

@media (min-width: 60em) {
  .section-head {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: end;
    column-gap: var(--space-7);
    margin-bottom: var(--space-7);
  }

  .section-head .eyebrow,
  .section-head .section-title {
    grid-column: 1;
  }

  .section-head .eyebrow {
    margin-bottom: 0;
  }

  .section-head .lead,
  .section-head .note {
    grid-column: 2;
    grid-row: 1 / span 2;
  }
}

@media (min-width: 60em) {
  .section-head-stack {
    grid-template-columns: minmax(0, 1fr);
  }

  .section-head-stack .eyebrow,
  .section-head-stack .section-title,
  .section-head-stack .lead,
  .section-head-stack .note {
    grid-column: 1;
    grid-row: auto;
  }
}

.section-title {
  font-size: var(--fs-2xl);
}

.inclusion-line {
  margin: var(--space-4) 0 0;
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--ink);
  max-width: 40ch;
}

.section-foot {
  margin-top: var(--space-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: center;
}

/* --------------------------------------------------------------------------
   6. Site header and nav drawer

   The drawer is only collapsed when JavaScript is running. main.js must add
   the class "js" to <html> as its first action. Without it the nav renders as
   a plain inline list and the toggle button stays hidden, so the site is fully
   usable with JavaScript off or broken.
   -------------------------------------------------------------------------- */

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

.site-header-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  max-width: calc(var(--maxw) + var(--gutter) * 2);
  min-height: var(--header-h);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-block: var(--space-3);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  margin-right: auto;
  text-decoration: none;
  color: var(--ink);
}

.brand-mark {
  /* The Emblem carries fine linework ("EST. 1898", the tool heads) that
     turns to mush below ~3rem, which reads as aliasing. Render it larger;
     --header-h grows in step so the header keeps its breathing room. */
  width: 3rem;
  height: 3rem;
  flex: none;
}

.brand-text {
  display: grid;
  gap: 0.1rem;
}

.brand-name {
  font-family: var(--font-display);
  font-size: 1.125rem;
  /* Brother 1816 ships in one weight here (Medium, 500). A 600 request would
     invite a synthesised faux bold, which smears a geometric face badly. */
  font-weight: 500;
  font-synthesis: none;
  line-height: 1.05;
  letter-spacing: 0.01em;
}

.brand-est {
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  /* Letterspaced caps: allow wrap without colliding (never wraps today - hidden
     under 48em - but keep a wrapping-safe leading if copy grows). */
  line-height: 1.4;
}

.brand:hover .brand-name {
  color: var(--sage-deep);
}

/* Keep the header on one row on small screens */
@media (max-width: 47.99em) {
  .brand-est {
    display: none;
  }
}

@media (min-width: 60em) {
  .brand-mark {
    width: 3.75rem;
    height: 3.75rem;
  }

  .brand-name {
    font-size: 1.3125rem;
  }
}

/* Toggle button - hidden until JavaScript proves itself */

.nav-toggle {
  display: none;
  align-items: center;
  gap: 0.55rem;
  padding: 0.55rem 0.75rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

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

.nav-toggle:hover {
  background: var(--shell);
  border-color: var(--ink);
}

.nav-toggle-box {
  position: relative;
  display: block;
  width: 1.125rem;
  height: 0.75rem;
  flex: none;
}

/* The word "Menu" beside the bars. Block with a flat line height so the
   button's height is set by the padding rather than by the font's leading,
   which keeps the bars optically centred against the label. */

.nav-toggle-label {
  display: block;
  /* Single short word ("Menu"); flat leading keeps the bars optically centred.
     Optical nudge, not a scale step - do not convert to --lh-*. */
  line-height: 1;
  white-space: nowrap;
}

.nav-toggle-bar,
.nav-toggle-bar::before,
.nav-toggle-bar::after {
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  background: currentColor;
  transition: transform var(--dur) var(--ease), opacity var(--dur-fast) linear;
}

.nav-toggle-bar {
  top: calc(50% - 1px);
}

.nav-toggle-bar::before,
.nav-toggle-bar::after {
  content: "";
}

.nav-toggle-bar::before {
  top: -5px;
}

.nav-toggle-bar::after {
  top: 5px;
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bar {
  transform: rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bar::before {
  top: 0;
  transform: rotate(-90deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bar::after {
  opacity: 0;
}

/* The nav itself */

.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4) var(--space-5);
  width: 100%;
}

.nav-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav-link {
  display: inline-block;
  padding-block: 0.35rem;
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  line-height: 1.4;
}

.nav-link:hover {
  color: var(--ink);
  border-bottom-color: var(--sage);
}

.nav-link[aria-current="page"] {
  color: var(--ink);
  border-bottom-color: var(--sage-deep);
}

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

/* Drawer behaviour, JavaScript only, below the nav breakpoint */

@media (max-width: 59.99em) {
  .js .site-nav {
    position: fixed;
    z-index: 60;
    inset: var(--header-h) 0 0 auto;
    width: min(21rem, 86vw);
    height: calc(100dvh - var(--header-h));
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: var(--space-2);
    padding: var(--space-5) var(--space-5) var(--space-7);
    overflow-y: auto;
    background: var(--paper-2);
    border-left: 1px solid var(--line);
    box-shadow: var(--shadow-lg);
    transform: translateX(100%);
    visibility: hidden;
    transition: transform var(--dur) var(--ease),
      visibility 0s linear var(--dur);
  }

  .js .nav-toggle[aria-expanded="true"] ~ .site-nav {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--dur) var(--ease), visibility 0s linear 0s;
  }

  .js .nav-list {
    flex-direction: column;
    gap: 0;
    width: 100%;
  }

  .js .nav-list li + li {
    border-top: 1px solid var(--line);
  }

  .js .nav-link {
    display: block;
    padding-block: var(--space-4);
    border-bottom: 0;
    border-left: 2px solid transparent;
    padding-left: var(--space-3);
    margin-left: calc(var(--space-3) * -1);
  }

  .js .nav-link[aria-current="page"] {
    border-left-color: var(--sage-deep);
  }

  .js .nav-cta {
    margin-left: 0;
    margin-top: var(--space-5);
    width: 100%;
    justify-content: center;
  }

  /* Scrim behind the open drawer */
  .js .site-header::after {
    content: "";
    position: fixed;
    inset: var(--header-h) 0 0 0;
    z-index: 55;
    background: var(--ink-70);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--dur) var(--ease), visibility 0s linear var(--dur);
  }

  .js .site-header:has(.nav-toggle[aria-expanded="true"])::after {
    opacity: 1;
    visibility: visible;
    transition: opacity var(--dur) var(--ease), visibility 0s linear 0s;
  }

  body:has(.nav-toggle[aria-expanded="true"]) {
    overflow: hidden;
  }
}

@media (min-width: 60em) {
  .site-header-inner {
    flex-wrap: nowrap;
  }

  /* The toggle is a mobile control only */
  .js .nav-toggle {
    display: none;
  }

  .site-nav {
    width: auto;
    gap: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   7. Hero
   Full bleed placeholder image with an offset paper panel over it, so the
   copy never sits on the image itself and contrast is guaranteed.
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  background: var(--shell);
}

.hero-media {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--shell-2);
}

.hero-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The hero film. Hidden below 48em (the wide-frame breakpoint) and inert
   without JS: only main.js adds .is-playing, and only when reduced motion
   is off. Rests on its final frame after one play. */
.hero-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  display: none;
}

@media (min-width: 48em) {
  .hero-media video {
    display: block;
  }

  .js .hero-media video {
    transition: opacity var(--dur-slow) var(--ease);
  }

  .js .hero-media video.is-playing {
    opacity: 1;
  }
}

.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(25, 23, 19, 0.05) 0%,
    rgba(25, 23, 19, 0) 45%
  );
}

.hero-inner {
  position: relative;
  width: 100%;
  max-width: calc(var(--maxw) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.hero-panel {
  position: relative;
  margin-top: -3rem;
  /* Inset text from the tinted/paper panel edge (never kiss the border). */
  padding: var(--space-6) var(--space-5) var(--space-7);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
}

.hero-lockup {
  width: clamp(11rem, 42vw, 15rem);
  height: auto;
  margin-bottom: var(--space-5);
}

.hero-title {
  margin: 0 0 var(--space-4);
  max-width: 18ch;
}

.hero-lead {
  margin: 0;
  max-width: 44ch;
  color: var(--ink-soft);
  font-size: var(--fs-md);
  font-weight: var(--fw-lead);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

.hero-actions .btn {
  flex: 1 1 14rem;
  justify-content: center;
}

@media (min-width: 48em) {
  .hero-media {
    aspect-ratio: 16 / 9;
  }

  .hero-panel {
    margin-top: -5rem;
    padding: var(--space-7) var(--space-7) var(--space-7);
    max-width: 40rem;
  }

  .hero-actions .btn {
    flex: 0 0 auto;
  }
}

@media (min-width: 75em) {
  .hero-media {
    aspect-ratio: 21 / 9;
    max-height: 40rem;
  }

  .hero-panel {
    margin-top: -8rem;
    max-width: 44rem;
  }
}

/* Compact page header used by inner pages instead of the hero */

.page-head {
  padding-block: calc(var(--step) * 0.75) calc(var(--step) * 0.5);
  background: var(--shell);
  border-bottom: 1px solid var(--line-strong);
}

.page-head .page-title {
  margin: 0;
  max-width: 20ch;
}

.page-head .lead {
  margin-top: var(--space-4);
}

/* Contact page: the enquiry lead sits under the title and centres, so it
   cannot hang off to the right the way a two-column .section-head lead does
   from 60em. */
.page-head-centre .section-inner {
  text-align: center;
}

.page-head-centre .page-title,
.page-head-centre .lead,
.page-head-centre .inclusion-line {
  margin-inline: auto;
}

.page-head-centre .lead {
  max-width: 42ch;
}

/* --------------------------------------------------------------------------
   8. Buttons
   Square shouldered by design. No fully rounded pills anywhere.
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  min-height: 2.875rem;
  padding: 0.75rem 1.35rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  /* Letterspaced multi-line caps need room; 1.4 clears wrap at 375px. */
  line-height: 1.4;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease),
    transform var(--dur-fast) var(--ease);
}

.btn:active {
  transform: translateY(1px);
}

/* Primary buttons wear the third brand colour flat: Painswick sage with an
   ink label (8.2:1). The client's second-round note is explicit - "sage, not
   yellow or red", "one colour Pantone" - so rest, hover and pressed states
   all stay inside the sage ramp. Hover brightens toward the availability
   band's green rather than darkening (client wants the sage "popping",
   27 Aug 2026); the ink label stays put at 8.6:1. The label, not the fill,
   is what identifies the control against the page ground, and the label is
   gated by the contrast checker. */
.btn-primary {
  color: var(--ink);
  background: var(--sage);
  border-color: var(--sage);
}

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

.btn-ghost {
  color: var(--ink);
  background: transparent;
  border-color: var(--ink);
}

.btn-ghost:hover {
  color: var(--paper);
  background: var(--ink);
}

/* The counter-button for the sage availability band, where a sage fill would
   vanish into its own ground. Ink at rest, deep sage on hover - both label
   pairs (paper/ink 16:1, paper/sage-deep 5.7:1) are gated by the checker. */
.btn-ink {
  color: var(--paper);
  background: var(--ink);
  border-color: var(--ink);
}

.btn-ink:hover {
  color: var(--paper);
  background: var(--sage-deep);
  border-color: var(--sage-deep);
}

.btn-sm {
  min-height: 2.375rem;
  padding: 0.5rem 1rem;
  font-size: var(--fs-xs);
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.section-dark .btn-ghost {
  color: var(--paper);
  border-color: var(--paper-16);
}

.section-dark .btn-ghost:hover {
  color: var(--ink);
  background: var(--sage-bright);
  border-color: var(--sage-bright);
}

.section-dark .btn-primary {
  color: var(--ink);
  background: var(--sage);
  border-color: var(--sage);
}

.section-dark .btn-primary:hover {
  color: var(--ink);
  background: var(--sage-bright);
  border-color: var(--sage-bright);
}

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

/* --------------------------------------------------------------------------
   9. Media frames, cards and grids
   -------------------------------------------------------------------------- */

.media-frame {
  position: relative;
  overflow: hidden;
  background: var(--shell-2);
  border-radius: var(--radius);
}

.media-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease);
}

.card-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: 1fr;
}

@media (min-width: 48em) {
  .card-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-6);
  }
}

@media (min-width: 60em) {
  .card-grid-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.card {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.card-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
}

.card-title {
  margin: 0;
  font-size: var(--fs-lg);
}

.card-text {
  margin: 0;
  color: var(--ink-soft);
}

.card-body .link-arrow,
.card-body .btn {
  margin-top: auto;
  align-self: flex-start;
}

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

a:hover > .media-frame img,
.gallery-figure:hover .media-frame img {
  transform: scale(1.035);
}

/* Numbered "moments" list - how it works */

.moment-list {
  display: grid;
  gap: var(--space-5);
  counter-reset: moment;
}

@media (min-width: 48em) {
  .moment-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-6) var(--space-7);
  }
}

.moment {
  padding-top: var(--space-4);
  border-top: 2px solid var(--ink);
  counter-increment: moment;
}

.moment::before {
  content: counter(moment, decimal-leading-zero);
  display: block;
  margin-bottom: var(--space-3);
  color: var(--sage-deep);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.4;
}

.moment-title {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-md);
  font-family: var(--font-body);
  font-weight: 700;
  letter-spacing: 0;
}

.moment-text {
  margin: 0;
  color: var(--ink-soft);
}

/* Split block - image beside copy */

.split {
  display: grid;
  gap: var(--space-5);
  align-items: center;
}

.split-media .media-frame {
  aspect-ratio: 3 / 2;
  border-radius: var(--radius-lg);
}

.split-body > * + * {
  margin-top: var(--space-4);
}

@media (min-width: 60em) {
  .split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--space-8);
  }

  .split-reverse .split-media {
    order: 2;
  }
}

/* A split whose columns should not be vertically centred - used where a short
   prose column sits beside a tall form and centring would strand it. */
@media (min-width: 60em) {
  .split-top {
    align-items: start;
  }
}

/* Frame ratio override for a 1200x900 image in a split, so the caption plate
   drawn inside the placeholder is not cropped away. Keep the ratio of the
   frame matched to the ratio of the image it holds. */
.split-media .media-frame-4x3 {
  aspect-ratio: 4 / 3;
}

/* Portrait frame for the real photography: the source set is almost entirely
   portrait, so splits that hold a real shot use 4:5 rather than cropping a
   tall frame into a letterbox. The gallery equivalent lives in section 10,
   after the base gallery frame rules it must override. */
.split-media .media-frame-portrait {
  aspect-ratio: 4 / 5;
}

/* Square frame for a genuinely 1:1 image in a split (the plaque crop on the
   contact page). A 4:5 frame would shear ten percent off each side. */
.split-media .media-frame-square {
  aspect-ratio: 1 / 1;
}

/* Tick list of inclusions */

.tick-list {
  display: grid;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 48em) {
  .tick-list {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-3) var(--space-6);
  }
}

.tick-list li {
  position: relative;
  padding-left: 1.9rem;
  color: var(--ink-soft);
}

.tick-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 0.7rem;
  height: 0.7rem;
  background: var(--sage-strong);
  border-radius: 1px;
}

/* Amenity grid - inclusions as equal fields (Colony idea, Workshop voice).
   Sage icons as recurring small marks; photography and facts stay the
   premium. The icons are inline SVGs drawn on a 24px grid, stroke 1.5 with
   square caps - first party, so there is no icon font to fail to load and
   dump its ligature words into the page, which is exactly what retired the
   Google Material Symbols subset on 18 Aug 2026. */

.amenity-icon {
  display: block;
  width: 2.125rem;
  height: 2.125rem;
  color: var(--sage-deep);
}

/* The glyphs sit inside the 24px grid with 3 to 4 units of clear space on
   the left, so an icon box flush with its label looks indented. Pull the box
   back by that margin and the drawn stroke shares the label's left edge. */
.amenity-grid .amenity-icon {
  margin-inline-start: -0.1875rem;
}

.inclusions .section-head {
  margin-bottom: var(--space-7);
}

/* "One Office, One Price, One Invoice": three beats. Where the title must
   wrap, 12ch gives one beat per line; from 48em the whole line fits the
   measure, so it runs as one. Never the balanced two-line break, which
   splits a beat ("One Office, One / Price, One Invoice"). */
.inclusions .section-title {
  max-width: 12ch;
}

@media (min-width: 48em) {
  .inclusions .section-title {
    max-width: none;
  }
}

/* Twelve amenities as equal fields: two up on a phone, three up from 40em,
   four up from 60em. Every row is full at every width, and the field width
   stays in proportion to the 16ch / 18ch label blocks, so no column carries
   a wide empty right half. */
.amenity-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-5) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 40em) {
  .amenity-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-6) var(--space-5);
  }
}

@media (min-width: 60em) {
  .amenity-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-7) var(--space-6);
  }
}

.amenity {
  display: grid;
  justify-items: start;
  align-content: start;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  min-height: 0;
}

.amenity-label {
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: var(--lh-snug);
  text-wrap: pretty;
  max-width: 16ch;
}

@media (min-width: 60em) {
  .amenity-label {
    max-width: 18ch;
  }
}

.amenity-note {
  margin-top: var(--space-7);
  max-width: 62ch;
}

/* Fact strip */

.fact-list {
  display: grid;
  gap: var(--space-5);
  margin: 0;
  padding: 0;
}

/* Breathing room when a fact strip follows another block in the same
   section, for example the terms callout on the offices page. */
* + .fact-list {
  margin-top: var(--space-6);
}

@media (min-width: 48em) {
  .fact-list {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-6);
  }
}

.fact dt,
.fact-term {
  margin: 0 0 var(--space-2);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.4;
}

.fact dd,
.fact-detail {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
}

/* Callout / note */

.callout {
  padding: var(--space-5);
  background: var(--shell);
  border-left: 3px solid var(--sage-strong);
  border-radius: 0 var(--radius) var(--radius) 0;
}

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

.note {
  color: var(--muted);
  font-size: var(--fs-sm);
}

/* --------------------------------------------------------------------------
   10. Gallery - plain CSS grid of figures. No lightbox, no carousel.
   -------------------------------------------------------------------------- */

.gallery-group + .gallery-group {
  margin-top: var(--step);
}

.gallery-group-title {
  margin: 0 0 var(--space-5);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-xl);
}

.gallery-grid {
  display: grid;
  /* Row gap holds caption (fs-sm ~1 line) plus one unit of air. */
  gap: var(--space-5) var(--space-4);
  grid-template-columns: 1fr;
}

@media (min-width: 40em) {
  .gallery-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-6) var(--space-5);
  }
}

/* A three-figure group goes three up as soon as the frames are usable
   (about 215px at 48em), never two up: two-up leaves one figure alone in
   the last row. Four-figure groups wait for 64em. */
@media (min-width: 48em) {
  .gallery-grid-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 64em) {
  .gallery-grid-4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.gallery-figure {
  margin: 0;
  display: grid;
  gap: var(--space-3);
}

.gallery-figure .media-frame {
  aspect-ratio: 3 / 2;
  border-radius: var(--radius);
}

.gallery-figure-square .media-frame {
  aspect-ratio: 1 / 1;
}

/* The lead figure of a group is a 16:9 image; give it the matching frame and
   let it span the full grid width as soon as the grid has two columns, so it
   reads as the establishing shot rather than a cropped cell. */
.gallery-figure-wide .media-frame {
  aspect-ratio: 16 / 9;
}

@media (min-width: 40em) {
  .gallery-figure-wide {
    grid-column: 1 / -1;
  }
}

/* The office gallery holds 1200x900 images; match the frames so the caption
   plates drawn inside the placeholders survive uncropped. */
.gallery-grid-3 .media-frame {
  aspect-ratio: 4 / 3;
}

/* Portrait gallery cells for the real photography. Declared after the base
   3:2 and 4:3 frame rules above because it must win at equal specificity. */
.gallery-figure-portrait .media-frame {
  aspect-ratio: 4 / 5;
}

/* Non-spanning 16:9 cells for the DJI set, which is landscape throughout.
   Distinct from -wide, which spans the grid as the establishing shot. */
.gallery-figure-landscape .media-frame {
  aspect-ratio: 16 / 9;
}

.gallery-caption {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-sm);
}

/* --------------------------------------------------------------------------
   11. FAQ - native details and summary, no JavaScript
   -------------------------------------------------------------------------- */

/* Each group is a card a step darker than the page ground, so the group
   toggle visibly belongs to the questions it controls. Text pairs on shell
   (ink, ink-soft, sage-deep) are all gated by the contrast checker. */
.faq-group {
  background: var(--shell);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
}

@media (min-width: 40em) {
  .faq-group {
    padding: var(--space-6);
  }
}

.faq-group + .faq-group {
  margin-top: var(--space-6);
}

.faq-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin: 0 0 var(--space-4);
}

.faq-group-title {
  margin: 0;
  font-size: var(--fs-xl);
}

/* Open-all / close-all for one group. The glyph is the same two-gradient
   plus that every .faq-question::after wears, so the control reads as "this,
   but for the whole section". Shipped [hidden]; main.js reveals it, exactly
   like the office filter bar. */
.faq-group-toggle {
  position: relative;
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  background: transparent;
  border: 1px solid var(--sage-strong);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
    background-color var(--dur-fast) var(--ease);
}

.faq-group-toggle::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 0.85rem;
  height: 0.85rem;
  background-image: linear-gradient(var(--sage-deep), var(--sage-deep)),
    linear-gradient(var(--sage-deep), var(--sage-deep));
  background-position: center, center;
  background-size: 100% 2px, 2px 100%;
  background-repeat: no-repeat;
  transition: transform var(--dur) var(--ease),
    background-size var(--dur) var(--ease);
}

.faq-group-toggle.is-open::after {
  background-size: 100% 2px, 2px 0;
  transform: rotate(180deg);
}

.faq-group-toggle:hover {
  border-color: var(--sage-deep);
}

.faq-list {
  border-top: 1px solid var(--line);
}

.faq-item {
  border-bottom: 1px solid var(--line);
  background: transparent;
}

.faq-question {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  list-style: none;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--ink);
  transition: color var(--dur-fast) var(--ease);
}

.faq-question::-webkit-details-marker {
  display: none;
}

.faq-question::marker {
  content: "";
}

.faq-question::after {
  content: "";
  position: relative;
  flex: none;
  width: 0.85rem;
  height: 0.85rem;
  margin-top: 0.4em;
  background-image: linear-gradient(var(--sage-deep), var(--sage-deep)),
    linear-gradient(var(--sage-deep), var(--sage-deep));
  background-position: center, center;
  background-size: 100% 2px, 2px 100%;
  background-repeat: no-repeat;
  transition: transform var(--dur) var(--ease),
    background-size var(--dur) var(--ease);
}

/* Open: the upright stroke collapses, leaving a minus */
.faq-item[open] .faq-question::after {
  background-size: 100% 2px, 2px 0;
  transform: rotate(180deg);
}

.faq-question:hover {
  color: var(--sage-deep);
}

.faq-answer {
  padding: 0 0 var(--space-5);
  max-width: 68ch;
  color: var(--ink-soft);
}

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

.faq-item[open] .faq-answer {
  animation: faq-open var(--dur) var(--ease) both;
}

@keyframes faq-open {
  from {
    opacity: 0;
    transform: translateY(-0.35rem);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   12. Forms
   -------------------------------------------------------------------------- */

.form {
  display: grid;
  gap: var(--space-5);
  padding: var(--space-5);
  background: var(--paper-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  /* Cap the measure: a field stretched across the full content width reads
     like a spreadsheet, not a letter. Splits are narrower than this anyway. */
  max-width: 46rem;
}

@media (min-width: 48em) {
  .form {
    padding: var(--space-6);
  }
}

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

.field-label {
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink);
  line-height: 1.4;
}

.field-optional {
  color: var(--muted);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}

.field-hint {
  color: var(--muted);
  font-size: var(--fs-sm);
  max-width: 70ch;
}

.field-input,
.field-select,
.field-textarea {
  width: 100%;
  min-height: 2.875rem;
  padding: 0.7rem 0.85rem;
  color: var(--ink);
  background: var(--paper-2);
  border: 1px solid var(--line-control);
  border-radius: var(--radius);
  font-size: var(--fs-base);
  transition: border-color var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease);
}

.field-textarea {
  min-height: 8.5rem;
  resize: vertical;
  line-height: var(--lh-body);
}

.field-select {
  appearance: none;
  padding-right: 2.5rem;
  background-image: linear-gradient(
      45deg,
      transparent 50%,
      var(--ink-soft) 50%
    ),
    linear-gradient(135deg, var(--ink-soft) 50%, transparent 50%);
  background-position: right 1.15rem center, right 0.8rem center;
  background-size: 0.4rem 0.4rem, 0.4rem 0.4rem;
  background-repeat: no-repeat;
}

.field-input::placeholder,
.field-textarea::placeholder {
  color: var(--muted);
  opacity: 1;
}

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

/* A transparent outline rather than `outline: none`. The sage ring below is
   the visible focus indicator in normal rendering, but forced-colours mode
   strips box-shadow while honouring outline suppression, which would leave
   keyboard focus invisible on every field. A transparent outline is repainted
   in the user's own system colour there, so focus survives either way. */
.field-input:focus,
.field-select:focus,
.field-textarea:focus {
  outline: 2px solid transparent;
  outline-offset: 2px;
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(93, 97, 86, 0.35);
}

.field-error {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  color: var(--brick);
  font-size: var(--fs-sm);
  font-weight: 500;
}

.field-error::before {
  content: "\26A0";
  font-size: 0.9em;
}

.field-input[aria-invalid="true"],
.field-select[aria-invalid="true"],
.field-textarea[aria-invalid="true"] {
  border-color: var(--brick);
  border-width: 2px;
  background: #fdf6f3;
}

.field-input[aria-invalid="true"]:focus,
.field-select[aria-invalid="true"]:focus,
.field-textarea[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 3px rgba(156, 79, 51, 0.28);
}

/* Honeypot - must never be seen or focused by a human */

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

.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

.form-status {
  padding: var(--space-5);
  border-radius: var(--radius);
  border: 1px solid var(--line-strong);
  background: var(--shell);
  font-size: var(--fs-sm);
}

.form-status[data-state="error"] {
  color: var(--brick);
  border-color: var(--brick);
  background: #fdf6f3;
}

.form-status[data-state="success"] {
  color: var(--ok);
  border-color: var(--ok);
  background: #eef4ef;
}

.form-summary {
  /* Inset text from the tinted error panel edge. */
  padding: var(--space-5);
  border: 2px solid var(--brick);
  border-radius: var(--radius);
  background: #fdf6f3;
}

.form-summary-title {
  margin: 0 0 var(--space-2);
  font-family: var(--font-body);
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--brick);
}

.form-summary ul {
  margin: 0;
  padding-left: 1.2rem;
}

.form-summary a {
  color: var(--brick);
}

/* Address block - used instead of a third party map embed */

.address-block {
  display: grid;
  gap: var(--space-4);
  /* Inset from the ink tint: --space-5 (1.5rem) is at least a cap-height at
     375px; hatch is a background image so it never underlies glyph ink via
     blend - text paints above it in normal flow. */
  padding: var(--space-5);
  background: var(--ink);
  color: var(--paper);
  border-radius: var(--radius-lg);
  background-image: repeating-linear-gradient(
    135deg,
    rgba(245, 242, 236, 0.05) 0 2px,
    transparent 2px 10px
  );
}

.address-block .eyebrow {
  color: var(--sage);
  margin-bottom: 0;
}

.address-block address {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-style: normal;
  line-height: 1.35;
}

.address-block a {
  color: var(--paper);
  text-decoration-color: var(--sage);
}

.address-block a:hover {
  color: var(--sage);
}

.directions-list {
  display: grid;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.directions-list li {
  position: relative;
  padding-left: 2.25rem;
  counter-increment: step;
  color: var(--ink-soft);
}

.directions-list li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  display: grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  color: var(--paper);
  background: var(--ink);
  border-radius: var(--radius);
  font-size: var(--fs-xs);
  font-weight: 700;
}

/* Route map - the drawn walking diagram on the contact page, the first-party
   companion to the address block's no-embed decision. The inline SVG carries
   hard hex fills so the artwork previews correctly on its own; these rules win
   the cascade in situ, so the drawing re-skins from the tokens (including the
   print block's paper-to-white swap). */

.route-map {
  margin: 0;
  overflow: hidden;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}

.route-map svg {
  display: block;
  width: 100%;
  height: auto;
}

/* The inline SVG carries font-family and colour presentation attributes as a
   no-CSS fallback. CSS beats a presentation attribute, so these rules are what
   actually paint the map - and they must, or the map stops following the theme.
   Keep every visual property of the map tokenised here, never only in the SVG. */
.route-map text { font-family: var(--font-body); }
.route-map-ground { fill: var(--paper); }
.route-map-street { stroke: var(--line-strong); }
.route-map-route { stroke: var(--sage-deep); }
.route-map-station { fill: var(--paper); stroke: var(--sage-deep); }
.route-map-plate { fill: var(--ink); }
.route-map-plate-keyline { stroke: var(--paper-2); }
.route-map-label { fill: var(--ink); }
.route-map-street-label { fill: var(--ink-soft); }
.route-map-annotation { fill: var(--sage-deep); }
.route-map-note { fill: var(--muted); }

/* Below 40em the diagram scales past ~0.5x and the street names stop earning
   their size. The streets stay; their labels go. The primary labels, the route
   and the "10 min" claim are the load-bearing content. */
@media (max-width: 39.99em) {
  .route-map-street-label {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   12b. Floor groups - offices page structure
   Grouping the office cards by floor is page structure, so it lives here.
   Card, badge and summary styling stay in availability.css (the module).
   -------------------------------------------------------------------------- */

.floor-group + .floor-group {
  margin-top: var(--space-7);
}

.floor-group-title {
  margin: 0 0 var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-xl);
}

.floor-group-note {
  margin: 0 0 var(--space-5);
  max-width: 62ch;
}

/* The Meeting Room's inclusion line. A site-owned class, not a restyle of
   any module class - the module's .office-card-meta stays muted. Sage-deep
   is 6.08:1 on the card's paper-2. */
.office-card-highlight {
  margin: 0;
  color: var(--sage-deep);
  font-size: var(--fs-sm);
  font-weight: 700;
}

/* Four available cards on the ground floor want 2+2, not the module's
   three-up 3+1. Compact occupied cards on the first floor are a denser
   four-up so they do not compete with the available stock below. */
@media (min-width: 64em) {
  .floor-group:not(.meeting-room-group) .office-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .floor-group:not(.meeting-room-group) .office-grid-compact {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.floor-group .office-grid + .office-grid {
  margin-top: var(--space-5);
}

/* Occupied cards: muted so available stock leads. More specific than the
   module's .office-card so the site stylesheet still wins despite loading
   first. */
.floor-group .occupied-card {
  background: var(--shell);
}

.floor-group .occupied-card:hover {
  box-shadow: none;
  border-color: var(--line-strong);
}

/* Filter pills. Buttons, not links: they change what the page shows, they
   go nowhere. Hidden until main.js removes [hidden] from the bar, so a
   no-JavaScript visitor simply sees every office. The pressed state swaps
   to a solid sage fill (ink on sage 8.2:1) plus a weight change, so state
   is never colour alone. */
.office-filter {
  display: grid;
  gap: var(--space-3);
}

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

.office-filter-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.filter-pill {
  min-height: 2.375rem;
  padding: 0.45rem 1rem;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 500;
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

.filter-pill:hover {
  border-color: var(--ink);
}

.filter-pill[aria-pressed="true"] {
  background: var(--sage);
  border-color: var(--sage-strong);
  font-weight: 700;
}

/* The module gives .office-card display:flex, which would silently beat the
   user agent's [hidden] { display: none } - author styles win regardless of
   specificity. Filtered-out cards and emptied floor groups must actually
   disappear. */
.office-card[hidden],
.floor-group[hidden] {
  display: none;
}

/* A lone card in an office grid - The Meeting Room, and Office 5 after the
   four compact let cards - spans the grid and, from 48em, lays out as a
   plate: photograph left, body right. Half of a two-up grid with nothing
   beside it read as an orphan. The photograph keeps its 16:9 frame and
   stretches to the body where the body is taller, so it is never a strip.
   The split is even until 64em; the meeting room's CTA needs a body column
   of about 320px, which the wider split gives from there. :not([hidden])
   keeps the filter's [hidden] rule below in charge of hiding. */
.floor-group .office-grid:has(> .office-card:only-child) {
  grid-template-columns: 1fr;
}

@media (min-width: 48em) {
  .floor-group .office-grid:has(> .office-card:only-child) > .office-card:not([hidden]) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  .floor-group .office-grid:has(> .office-card:only-child) .office-card-media {
    aspect-ratio: 16 / 9;
    /* Both axes stretched: a ratio box only stretched in one axis derives
       the other from the ratio and overflows its column. */
    align-self: stretch;
    justify-self: stretch;
  }

  /* The body keeps the compact cards' rhythm: name, meta, badge, CTA at
     one gap, centred as a block against the photograph like every other
     split on the site. Without this the CTA's margin-top: auto (right for
     a grid of equal-height cards) pushed the button to the foot of a
     photograph-tall card, leaving a hole under the badge - the client
     asked for the spacing to match the cards above (9 Sep 2026). */
  .floor-group .office-grid:has(> .office-card:only-child) .office-card-body {
    justify-content: center;
  }

  .floor-group .office-grid:has(> .office-card:only-child) .office-card-body > a:last-child {
    margin-top: 0;
  }
}

@media (min-width: 64em) {
  .floor-group .office-grid:has(> .office-card:only-child) > .office-card:not([hidden]) {
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  }
}

/* The Meeting Room block reads like a floor group but is deliberately not
   one: it is shared space, not lettable stock, so it sits after both floors
   and outside the filter's reach. It takes the lone-card plate above and
   nothing else: on 9 Sep 2026 the centred icon grid that used to fill this
   body came out, because every fact it carried was already in the group
   note above it, the meta line beside it, or the inclusions grid below.
   Four centred cells of restated facts is decoration, not information. */

/* --------------------------------------------------------------------------
   13. Availability band
   The site's demarcation point: a full-width sage band carrying the
   availability count as display type and one call to action. Site level
   only - office cards and badges stay in availability.css. Every text pair
   on the band (ink/sage-band 8.18:1, ink-soft/sage-band 5.25:1) is gated
   by the contrast checker; the CTA is .btn-ink because a sage button
   cannot sit on a sage ground. Since 9 Sep 2026 the band is the same
   Painswick as the primary button (the client saw the two greens differ). ------------------------------------------- */

.availability-strip {
  color: var(--ink);
  background: var(--sage-band);
  border-block: 1px solid var(--sage-strong);
  padding-block: calc(var(--step) * 0.6);
}

.availability-strip .section-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-3);
  align-items: center;
}

.availability-strip .eyebrow {
  grid-column: 1 / -1;
  margin-bottom: 0;
  color: var(--ink-soft);
}

.availability-headline {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2rem, 1.3rem + 3.2vw, 3.5rem);
  font-weight: 500;
  font-synthesis: none;
  line-height: 1.05;
  letter-spacing: 0.005em;
}

.availability-headline strong {
  font-weight: 500;
}

.availability-strip .btn {
  justify-self: start;
}

@media (min-width: 48em) {
  .availability-strip .section-inner {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--space-6);
  }

  .availability-strip .btn {
    grid-column: 2;
    grid-row: 2;
    justify-self: end;
  }
}

/* --------------------------------------------------------------------------
   13b. Closing CTA band
   Replaces the last dark "office awaits" void: a tight paper band, ink
   type, no eyebrow, no floating headline, then the ink footer. The seam
   is the contrast; there is no dead scroll of matching dark on dark.
   -------------------------------------------------------------------------- */

.cta-band {
  color: var(--ink);
  background: var(--paper);
  border-top: 1px solid var(--line);
  padding-block: var(--space-7);
}

.cta-band .section-inner {
  display: grid;
  gap: var(--space-5);
  align-items: center;
}

.cta-band .section-title {
  margin: 0;
  max-width: 16ch;
}

/* Title beside the buttons only from 64em. The two buttons are about 530px
   as a row, and at 48em that left the title a 16ch column that broke one
   word per line; below 64em the band stacks, as on a phone. */
@media (min-width: 64em) {
  .cta-band .section-inner {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--space-6);
  }
}

/* --------------------------------------------------------------------------
   14. Footer
   -------------------------------------------------------------------------- */

.site-footer {
  margin-top: auto;
  color: var(--paper);
  background: var(--ink);
}

.site-footer-inner {
  display: grid;
  gap: var(--space-6);
  width: 100%;
  max-width: calc(var(--maxw) + var(--gutter) * 2);
  margin-inline: auto;
  padding: var(--step) var(--gutter) var(--space-7);
}

@media (min-width: 48em) {
  .site-footer-inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-7) var(--space-6);
  }
}

@media (min-width: 64em) {
  .site-footer-inner {
    grid-template-columns: 1.4fr 1fr 1.2fr 1.2fr;
  }
}

.footer-brand {
  display: grid;
  gap: var(--space-3);
  justify-items: start;
}

.footer-mark {
  /* Sized to span the "Ideas - Built - Crafted - Shared" tagline beneath it
     and fill the vertical room the footer's tallest column leaves on a
     1080p screen. At this scale the Emblem's fine linework finally renders
     the way the brand pack intends. */
  width: min(13rem, 100%);
  height: auto;
  /* The Emblem is an <img>, so its currentColor fills resolve to black - fine
     on the paper header, invisible on the ink footer. Inverting here is what
     paints it white-on-ink, the guidelines' approved combination. */
  filter: invert(1);
}

.footer-social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* A 24px glyph alone is under the 24x24 minimum target; the padding takes
     the hit area to 44px without moving the visual left edge. */
  padding: 0.625rem;
  margin: -0.625rem;
  color: #e6e0d4;
}

.footer-social:hover {
  color: var(--sage);
}

.footer-social svg {
  display: block;
}

.footer-tagline {
  margin: 0;
  color: var(--sage);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.4;
}

.footer-note {
  margin: 0;
  color: #cdc6b8;
  font-size: var(--fs-sm);
  max-width: 34ch;
}

.footer-col {
  display: grid;
  gap: var(--space-3);
  align-content: start;
}

.footer-heading {
  margin: 0;
  color: var(--paper);
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1.4;
}

.footer-links {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-links a {
  color: #e6e0d4;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 0.1em;
}

.footer-links a:hover {
  color: var(--paper);
  border-bottom-color: var(--sage);
}

.footer-address {
  margin: 0;
  color: #e6e0d4;
  font-style: normal;
  line-height: 1.5;
}

.site-footer a {
  color: #e6e0d4;
  text-decoration-color: var(--sage);
}

.site-footer a:hover {
  color: var(--sage);
}

.site-footer-base {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
  width: 100%;
  max-width: calc(var(--maxw) + var(--gutter) * 2);
  margin-inline: auto;
  padding: var(--space-5) var(--gutter) var(--space-6);
  border-top: 1px solid var(--paper-16);
}

.footer-legal {
  margin: 0;
  color: #b7b0a2;
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* --------------------------------------------------------------------------
   15. Motion
   The whole motion budget is here: the reveal fade up, the hover lift, the
   image zoom and the nav drawer. Nothing else moves.

   .reveal only hides itself once main.js has added the "js" class to <html>,
   so a failed script can never leave the page blank.
   -------------------------------------------------------------------------- */

.js .reveal {
  opacity: 0;
  transform: translateY(0.875rem);
  transition: opacity var(--dur-slow) var(--ease),
    transform var(--dur-slow) var(--ease);
  will-change: opacity, transform;
}

.js .reveal.is-visible {
  opacity: 1;
  transform: none;
  will-change: auto;
}

.js .reveal-delay-1 {
  transition-delay: 80ms;
}

.js .reveal-delay-2 {
  transition-delay: 160ms;
}

.js .reveal-delay-3 {
  transition-delay: 240ms;
}

/* --------------------------------------------------------------------------
   16. Focus
   Never remove the outline. Focus visible only, so mouse users do not see it.
   -------------------------------------------------------------------------- */

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

.section-dark :focus-visible,
.site-footer :focus-visible,
.address-block :focus-visible {
  outline-color: var(--sage);
}

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

.faq-question:focus-visible {
  outline-offset: -2px;
}

/* --------------------------------------------------------------------------
   17. Reduced motion
   -------------------------------------------------------------------------- */

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

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

  .js .reveal {
    opacity: 1;
    transform: none;
  }

  .card:hover {
    transform: none;
  }

  a:hover > .media-frame img,
  .gallery-figure:hover .media-frame img {
    transform: none;
  }

  .hero-media video {
    display: none !important;
  }
}

/* --------------------------------------------------------------------------
   18. Forced colours
   -------------------------------------------------------------------------- */

@media (forced-colors: active) {
  .btn,
  .card,
  .form,
  .field-input,
  .field-select,
  .field-textarea {
    border: 1px solid;
  }

  .nav-link[aria-current="page"] {
    border-bottom-color: Highlight;
  }
}

/* --------------------------------------------------------------------------
   19. Print
   -------------------------------------------------------------------------- */

@media print {
  :root {
    --paper: #fff;
    --shell: #fff;
  }

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

  .site-header,
  .nav-toggle,
  .site-nav,
  .skip-link,
  .hero-media,
  .hero-actions,
  .btn,
  .form,
  .site-footer-base {
    display: none !important;
  }

  .hero-panel {
    margin-top: 0;
    padding: 0;
    border: 0;
    box-shadow: none;
  }

  .section,
  .site-footer-inner {
    padding-block: 1rem;
  }

  .site-footer {
    color: #000;
    background: #fff;
    border-top: 1pt solid #000;
  }

  /* Print swaps the footer to white, where the inverted mark would vanish. */
  .footer-mark {
    filter: none;
  }

  .footer-note,
  .footer-address,
  .site-footer a,
  .footer-links a {
    color: #000;
  }

  .card,
  .callout,
  .address-block,
  .route-map {
    break-inside: avoid;
    color: #000;
    background: #fff;
    border: 1pt solid #000;
  }

  .faq-item {
    break-inside: avoid;
  }

  .faq-answer {
    display: block;
  }

  a[href^="http"]::after,
  a[href^="mailto"]::after {
    content: " (" attr(href) ")";
    font-size: 9pt;
    word-break: break-all;
  }

  .js .reveal {
    opacity: 1;
    transform: none;
  }
}
