/* ==========================================
   FONTS
   Served from this site, not from Google, so a visit sends nothing to anyone
   else. Inter and Outfit are variable fonts: one file per alphabet carries
   every weight. The Latin-extended files load only when a page shows one of
   their letters. index.html preloads Anton and Outfit (the first screen).
   ========================================== */
@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/anton-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/anton-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('assets/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('assets/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('assets/fonts/outfit-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('assets/fonts/outfit-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ==========================================
   DESIGN SYSTEM TOKENS & VARIABLES
   ========================================== */
:root {
  /* Color Palette */
  --primary: #101216;        /* Graphite black (brand dark) */
  --accent: #980000;         /* Deep brand red */
  --accent-light: #f4e6e6;   /* Faint red tint background */
  --accent-dark: #7a0000;    /* Darker red (hover / pressed) */
  /* The red for text on the dark sections and the header (only the logo's
     monogram, a picture, keeps the brand red): the brand red measures
     about 2:1 there, where small text needs 4.5:1 and even large text 3:1. */
  --accent-on-dark: #e5534b;

  /* The accent as an RGB triplet, for tints: rgba(var(--accent-rgb), 0.2);
     and the lighter red's, for an outline in it on a dark ground */
  --accent-rgb: 152, 0, 0;
  --accent-on-dark-rgb: 229, 83, 75;

  /* Deep brass for the signature card: dark enough to read on white (4.9:1),
     where lighter golds drop to about 2:1. */
  --brass: #8a6d1f;
  /* The same brass lifted for the dark grounds, where --brass all but
     disappears: the Google rating's stars (about 9:1 on the footer). */
  --gold: #d9ad4b;
  /* Warm off-white for the signature card, setting it apart from the white
     service cards without another saturated colour. */
  --paper: #faf7f1;

  --text: #2b2f36;           /* Neutral graphite for body copy */
  --text-muted: #6b7280;     /* Neutral grey for captions */
  --text-dark: #101216;      /* Brand graphite for heading contrast */
  --text-white: #ffffff;

  --bg-white: #ffffff;
  --bg-soft: #f6f7f8;        /* Neutral off-white background */
  --bg-dark: #0b0d10;        /* Deep graphite-black */

  /* OUTLINES. Every card, tile, field and button is drawn as a 1px outline,
     at the weight Our Process's card set: this on a dark ground, and
     --outline-on-light on a light one (the header's white bar and the
     logo's tile included). Hairlines that divide the inside of a card are
     fainter. */
  --outline-on-dark: rgba(255, 255, 255, 0.2);
  --outline-on-light: rgba(16, 18, 22, 0.16);

  /* TYPE. Every piece of text takes one of the styles at the end of this
     group (--type-*): a typeface, weight, size and line height in one value.

       font: var(--type-body-md);

     Each is named for what it is and how big: a heading, a label (the
     words on anything tapped or chosen: buttons, pills, links), body text,
     or a numeral, at a step of the size scale below. Letter-spacing and
     capitals can't go in a `font` value, so the few styles that take them
     say so, and the rule adds them beside it. New text takes a style that
     exists; a size that isn't on the scale is a new step, not a one-off.
     Icons are one em tall, so a font-size on an icon, its tile or its
     button sizes the icon, to suit that box: icons aren't on this scale. */

  /* Anton for display (it comes in one heavy weight, set as 400): the two
     display titles and the Google rating. Outfit for headings, labels and
     numerals, Inter for reading. */
  --font-display: 'Anton', var(--font-heading);
  --font-heading: 'Outfit', sans-serif;
  --font-body: 'Inter', sans-serif;

  /* The scale: 12, 14, 16, 18, 20, 24 and 32px. 12px is the floor on a
     phone. */
  --font-size-xs: 0.75rem;
  --font-size-sm: 0.875rem;
  --font-size-md: 1rem;
  --font-size-lg: 1.125rem;
  --font-size-xl: 1.25rem;
  --font-size-2xl: 1.5rem;
  --font-size-3xl: 2rem;
  /* ...and four that grow with the screen: the section titles, the
     subheads a step under them, the managed card's title and the hero's */
  --font-size-title: clamp(2rem, 5vw, 2.5rem);
  --font-size-subhead: clamp(1.5rem, 3vw, 2rem);
  --font-size-display: clamp(2.4rem, 5vw, 3.9rem);
  --font-size-hero: clamp(3.6rem, 9vw, 8.5rem);
  /* ...and a project's page title, a step above a section's at every
     width, as the subhead is a step under it (clamp(2rem, 4vw, 3rem) was
     the same as a section's on a phone and smaller on a tablet) */
  --font-size-page-title: clamp(2.5rem, 6.25vw, 3rem);

  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;
  --font-weight-bold: 700;
  --font-weight-heavy: 800;
  /* The logo's wordmark, "Alb Homes" beside the monogram: part of the logo
     rather than text, so it has a weight of its own, a hair under bold
     (Outfit is a variable font, so any weight draws) */
  --font-weight-logo: 650;

  --line-height-display: 0.95;   /* Anton's capitals, which have no descenders */
  --line-height-none: 1;         /* numerals */
  --line-height-snug: 1.1;       /* a page's title: big lines, close together */
  --line-height-tight: 1.25;     /* headings and labels: a button's padding
                                    makes its height, not its line */
  --line-height-normal: 1.6;     /* small text; the page's own */
  --line-height-relaxed: 1.7;    /* text to be read */

  --letter-spacing-tight: -0.02em;    /* the section titles */
  --letter-spacing-display: 0.01em;   /* Anton, opened up a hair */
  --letter-spacing-wide: 0.05em;      /* buttons and the menu */
  --letter-spacing-caps: 0.1em;       /* small capitals */

  /* The styles. Display: the hero's title and the managed card's, in
     capitals, and the Google rating, all with --letter-spacing-display. */
  --type-hero: var(--font-weight-regular) var(--font-size-hero)/var(--line-height-display) var(--font-display);
  --type-display: var(--font-weight-regular) var(--font-size-display)/var(--line-height-display) var(--font-display);
  /* A section's title (and a page's, on the legal pages), with
     --letter-spacing-tight */
  --type-title: var(--font-weight-heavy) var(--font-size-title)/var(--line-height-tight) var(--font-heading);
  /* A project's page title, a step above a section's, with
     --letter-spacing-tight: the one heading bigger than a section's */
  --type-page-title: var(--font-weight-heavy) var(--font-size-page-title)/var(--line-height-snug) var(--font-heading);
  /* A subhead: the heading of a part of a section, a step under the
     section's title at every width (the quote form's question and its
     summary's titles, the process's stage on show, the FAQs) */
  --type-subhead: var(--font-weight-bold) var(--font-size-subhead)/var(--line-height-tight) var(--font-heading);
  /* Headings: a card's title, a step's name */
  --type-heading-2xl: var(--font-weight-bold) var(--font-size-2xl)/var(--line-height-tight) var(--font-heading);
  --type-heading-xl: var(--font-weight-bold) var(--font-size-xl)/var(--line-height-tight) var(--font-heading);
  --type-heading-md: var(--font-weight-bold) var(--font-size-md)/var(--line-height-tight) var(--font-heading);
  /* Numerals: the hero's figures, the quote form's counts, and (at the
     hero's size) its slider's reading */
  --type-numeral-hero: var(--font-weight-heavy) var(--font-size-hero)/var(--line-height-none) var(--font-heading);
  --type-numeral-3xl: var(--font-weight-heavy) var(--font-size-3xl)/var(--line-height-none) var(--font-heading);
  --type-numeral-2xl: var(--font-weight-heavy) var(--font-size-2xl)/var(--line-height-none) var(--font-heading);
  --type-numeral-md: var(--font-weight-heavy) var(--font-size-md)/var(--line-height-none) var(--font-heading);
  /* Labels. A button is label-sm in capitals, with --letter-spacing-wide. */
  --type-label-lg: var(--font-weight-semibold) var(--font-size-lg)/var(--line-height-tight) var(--font-heading);
  --type-label-md: var(--font-weight-semibold) var(--font-size-md)/var(--line-height-tight) var(--font-heading);
  --type-label-sm: var(--font-weight-semibold) var(--font-size-sm)/var(--line-height-tight) var(--font-heading);
  --type-label-xs: var(--font-weight-semibold) var(--font-size-xs)/var(--line-height-tight) var(--font-heading);
  /* Body text: xl for the lead under a project's page title, lg for a lead
     under a section's title (balanced, like a heading), md for paragraphs,
     sm and xs for notes, hints and small print */
  --type-body-xl: var(--font-weight-regular) var(--font-size-xl)/var(--line-height-normal) var(--font-body);
  --type-body-lg: var(--font-weight-regular) var(--font-size-lg)/var(--line-height-relaxed) var(--font-body);
  --type-body-md: var(--font-weight-regular) var(--font-size-md)/var(--line-height-relaxed) var(--font-body);
  --type-body-sm: var(--font-weight-regular) var(--font-size-sm)/var(--line-height-normal) var(--font-body);
  --type-body-xs: var(--font-weight-regular) var(--font-size-xs)/var(--line-height-normal) var(--font-body);


  /* Corners: square, like a drawing, as Our Process first drew them. One
     radius for every box on the site (buttons, cards, panels, photos,
     fields, tiles and tags, the logo's tile included), a single pixel that
     takes the edge off without rounding it. Only the scrollbar stays round
     (--radius-round). */
  --radius: 1px;
  --radius-round: 9999px;

  /* Icons: every one a line drawing from Tabler Icons (the sprite at the
     top of index.html), drawn at one weight, in units of its 24-unit
     drawing: 1.6 comes to 1.5px on an icon beside 16px text (22.4px,
     .icon), and grows with the icon like type. No icon is filled, apart from the rating's stars (.stars),
     which fill in. */
  --icon-stroke: 1.6;

  /* Buttons (.btn): one height for every one, a fingertip's, the same on
     a phone as on a desktop (Alban, 27 September 2026: a phone's were
     52px, taller than a desktop's 48px); and the room at their sides,
     18px on a phone and 24px from 768px (the rule below: Alban, the same
     day, from 24, 20 and 16 drawn side by side) */
  --btn-h: 3rem;
  --btn-px: 18px;

  /* Spacing. --card-gap separates the cards of a grid or a row, --title-gap
     a section's title from what follows. --gutter, the page's side margin, is never narrower than
     --card-gap, so a card at the screen's edge sits no closer to it than to
     its neighbour. The larger values from 769px are in the rule below. */
  --card-gap: 1.5rem;
  --title-gap: 2.5rem;
  --gutter: 1.5rem;

  /* The room inside a card, the same all round: Our Process's card's,
     which the services' and the managed card, the contact cards and the
     FAQs share (Alban, 27 September 2026: theirs ran to 56px on a phone,
     where the quote form's and Our Process's 24px read better). 24px on
     a phone, 32px from 768px and 40px from 1101px, where Our Process
     changes its layout (the rules below). */
  --card-pad: 24px;

  /* The page's width: the container's (.container), the same in every
     section, the header and the footer, and what the portfolio's strip
     measures its padding from, so its cards line up with the rest. */
  --container-max: 1300px;

  /* Animation timing. The shared transitions name what a hover or a change
     of state may animate: colours, opacity and transform. Never `all`,
     which animates layout too.
     Every colour fades on --hover-fade, an even curve, in and out alike.
     A fill fades as --fill (registered under this block), never as
     background-color: Chrome runs a background-colour fade on its
     compositor and paints the fill's old colour for one frame as the fade
     ends, which flashed a service tile solid red at the end of its fade.
     --ease-out leaps a third of the way in its first frame, which for a
     colour reads as a snap: crossing from card to card snapped one tile
     off and the next on, and the header's links flicked to red. So it is
     kept for movement (an underline growing, a slide). */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  /* ...and for a box changing size or a long slide, where that leap jars,
     an even curve that starts and ends gently: the header's slide and the
     phone menu's fold */
  --ease-even: cubic-bezier(0.4, 0, 0.2, 1);
  /* One clock for a change of content inside a card, as Our Process's
     stage first had it: the box eases to its new height while the old
     words fade out over the new fading in, everything at once on
     --ease-out, with no stagger and no delay. Our Process's stage, and
     the quote form's question, its follow-ups, its plan and its summary,
     all keep it; and the height move
     is always the box's own, eased by script.js, never a piece inside it
     animating its size. */
  --move: 0.4s;
  --hover-fade: 0.3s ease-in-out;
  --transition: color var(--hover-fade), --fill var(--hover-fade),
    border-color var(--hover-fade),
    opacity 0.5s var(--ease-out), transform 0.5s var(--ease-out);
  --transition-fast: color var(--hover-fade), --fill var(--hover-fade),
    border-color var(--hover-fade),
    opacity 0.25s var(--ease-out), transform 0.25s var(--ease-out);

  /* The quote form's range sliders: the width of the invisible grab box the
     finger lands on. The input overhangs its drawn rail by half of it at each
     end, and script.js reads it to tell a press on the line from one
     elsewhere on the rail. */
  --slider-thumb: 56px;

  /* The phone menu's clocks. Opening, the header unfolds over --nav-move on
     the even curve, and the words wait --nav-pause, a moment, then fade in
     over --nav-fade, slower than a switch of content (Our Process's 0.4s),
     which is too quick for words arriving on a blank panel. Shutting, the
     words go quickly, over --nav-fade-out, while the header folds up at
     once on the same curve and clock as it opened: a fold that waited for
     the words felt unresponsive, and a quick one read as a glitch. The
     veil and the button's cross keep to --nav-move. */
  --nav-move: 0.45s;
  --nav-fade: 0.6s;
  --nav-pause: 0.15s;
  --nav-fade-out: 0.25s;

  /* The scrollbar's width, held open while the menu freezes the page (see
     body). script.js sets it; it is 0 otherwise, and on phones. */
  --lock-gutter: 0px;
}

/* A fill that changes with a state: a tile that lights up, a chosen
   answer's tint, a filled button's hover. The box takes `background-color:
   var(--fill)`, each state sets --fill, and --fill is what transitions,
   never background-color. Chrome hands a background-colour fade to its
   compositor (CompositeBGColorAnimation) and, on the frame the fade ends,
   paints the colour the fill had before it began: a tile flashed solid red
   as its fade-out ended, and grey as its fade-in did. Registered as a colour
   so that a fade interpolates it; it never inherits, so each box's fill is
   its own. */
@property --fill {
  syntax: '<color>';
  inherits: false;
  initial-value: transparent;
}

/* ==========================================
   RESET & FOUNDATIONS
   ========================================== */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* In-page links glide, unless the device asks for less motion. */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* No grey wash over whatever a finger taps on a phone, a button of the
     site's or not (a quote answer, a FAQ, a menu link): what it does shows
     the tap (CLAUDE.md). */
  -webkit-tap-highlight-color: transparent;
  /* The header's height: .header, the landing rule below and the
     phone menu's top padding are all set from it. It takes in the status
     bar where the page runs up under it (the site opened from an iPhone's
     home screen); in a browser's tab that's 0. Slimmer where the menu is a
     button (below), so the bar takes less of a small screen. */
  --header-h: calc(72px + env(safe-area-inset-top));
  /* How far a section tucks under the header when a link lands on it, so no
     sliver of the previous section shows between them. Raise it if a strip
     shows; lower it if a heading feels cramped. */
  --anchor-overlap: 26px;
  /* Whatever the page scrolls into view (a link's section, a field the
     keyboard's focus lands on) comes to rest under the pinned header, not
     behind it. */
  scroll-padding-top: var(--header-h);
}

@media (max-width: 1024px) {
  html {
    --header-h: calc(60px + env(safe-area-inset-top));
  }
}

/* Where a link to a section stops: clear of the pinned header (the page's
   scroll-padding, above), tucked under it by the overlap. */
section[id] {
  scroll-margin-top: calc(-1 * var(--anchor-overlap));
}

body {
  font-family: var(--font-body);
  color: var(--text);
  line-height: var(--line-height-normal);
  overflow-x: hidden;
  /* Freezing the page for the open menu takes the scrollbar away; this holds
     its width open so nothing shifts sideways. script.js measures it (0 on a
     phone, whose scrollbar floats over the page). */
  padding-right: var(--lock-gutter);
  /* so the menu's dimmer (body::after) covers the whole page */
  position: relative;
}

/* Frozen under the menu, the lightbox or the help panel (script.js,
   freezePage): a class rather than body's own style, which Plyr clears
   when it takes a player down. */
html.is-frozen body {
  overflow: hidden;
}

/* The window behind the page, which shows wherever a page is pulled past
   its top or bottom (a Mac's trackpad, an iPhone): the colour of the page's
   ends, so the pull shows more of the same. The legal pages are light from
   end to end. The home page opens on the hero's photo and closes on the
   dark footer, so its window is their dark, and turns white over the
   white sections, away from either end (script.js). Safari takes the
   window's colour from <body> whenever <body> has one of its own, so the
   light ground under the sections is <main>'s, and <body> has none. */
html {
  background-color: var(--bg-white);
}

html:has(.footer) {
  background-color: var(--bg-dark);
}

/* the light sections are white, their cards outlines on it */
main {
  background-color: var(--bg-white);
}

/* Every heading takes a style of its own below; this is the family look
   they share. One that runs to a second line shares its words evenly
   between them. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  color: var(--text-dark);
  font-weight: var(--font-weight-bold);
  line-height: var(--line-height-tight);
  text-wrap: balance;
}

a {
  text-decoration: none;
  color: inherit;
  transition: var(--transition-fast);
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

/* ICONS, drawn from the sprite at the top of index.html. An icon is 1.4em
   square, centred on the text's capitals; its lines take the text's colour
   and --icon-stroke. */
.icon {
  display: inline-block;
  height: 1.4em;
  width: auto;
  aspect-ratio: 1;
  overflow: visible;
  vertical-align: -0.325em;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.icon-spin {
  animation: icon-spin 2s linear infinite;
}

@keyframes icon-spin {
  to { transform: rotate(1turn); }
}

button, input, select, textarea {
  font-family: inherit;
  font-size: inherit;
  outline: none;
  border: none;
  background: none;
}

/* No transitions while the window's width is changing (script.js sets the
   class), or crossing a breakpoint animates the jump between layouts. */
.is-resizing *,
.is-resizing *::before,
.is-resizing *::after {
  transition: none !important;
}

/* Keyboard focus. The reset above removes the browser's outline;
   :focus-visible puts one back for keyboard focus only, so a click draws no
   ring. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* On the dark grounds the brand red measures about 2:1 against the
   background, too faint to find the focus by, so the ring is white there.
   The help card is white itself, so it keeps the red, as the header's
   white bar and the phone menu's white panel do. */
.hero :focus-visible,
.section-dark :focus-visible,
.footer :focus-visible {
  outline-color: var(--text-white);
}

.section-dark .help-card :focus-visible {
  outline-color: var(--accent);
}

/* A keyboard's way past the menu, straight to the page. Clipped away until
   it has the focus, not parked above the page: a pull past the top (an
   iPhone, a Mac's trackpad) shows what's up there. */
.skip-link {
  position: absolute;
  top: calc(0.75rem + env(safe-area-inset-top));
  left: max(0.75rem, env(safe-area-inset-left));
  z-index: 200;
  padding: 0.75rem 1.25rem;
  border-radius: var(--radius);
  background: var(--bg-white);
  color: var(--text-dark);
  font: var(--type-label-md);
}

.skip-link:not(:focus) {
  clip-path: inset(50%);
}

/* Read by screen readers, not shown. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ==========================================
   THE PAGE SCROLLBAR
   A transparent track, and a mid-grey thumb that shows on the light and the
   dark sections alike (graphite vanished on the dark ones). Unscoped on
   purpose; the sideways scrollers (the portfolio's strip, the quote form's
   plan) hide theirs.
   ========================================== */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

/* --text-muted (#6b7280) as an rgba, so the opacity can do the work */
::-webkit-scrollbar-thumb {
  background: rgba(107, 114, 128, 0.4);
  border-radius: var(--radius-round);
}

@media (hover: hover) {
  ::-webkit-scrollbar-thumb:hover {
    background: rgba(107, 114, 128, 0.7);
  }
}

@media (min-width: 769px) {
  :root {
    --card-gap: 2rem;
    --title-gap: 3rem;
    --gutter: 2.5rem;
  }
}

@media (min-width: 768px) {
  :root {
    --card-pad: 32px;
    --btn-px: 1.5rem;
  }
}

@media (min-width: 1101px) {
  :root {
    --card-pad: 40px;
  }
}

/* The standard properties, for Firefox and newer Chrome and Safari. */
html {
  scrollbar-width: thin;
  scrollbar-color: rgba(107, 114, 128, 0.4) transparent;
}

/* ==========================================
   REUSABLE UTILITIES & LAYOUTS
   ========================================== */
/* The page runs to the screen's edges (viewport-fit=cover, for Safari's
   floating bars), so a phone on its side puts the camera and the rounded
   corners over the page: the content keeps clear of them (the safe areas). */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 max(var(--gutter), env(safe-area-inset-right)) 0 max(var(--gutter), env(safe-area-inset-left));
}

.section {
  padding: 8rem 0;
  position: relative;
}

@media (max-width: 768px) {
  .section {
    padding: 5rem 0;
  }
}

.section-dark {
  background-color: var(--bg-dark);
  color: var(--bg-soft);
}

.section-dark h2,
.section-dark h3 {
  color: var(--text-white);
}

/* A section's title, centred over it; white in the dark sections (above) */
.section-title {
  text-align: center;
  font: var(--type-title);
  letter-spacing: var(--letter-spacing-tight);
  margin-bottom: var(--title-gap);
}

/* A title and its line with controls beside them (the portfolio's back,
   at the left, and next, at the right), level with the pair. The row is
   the section's width and no height, so it covers nothing between them:
   the controls stand out of it, centred on its line. */
.section-head {
  position: relative;
}

.section-head-actions {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* the line under a title, which then takes the title's gap below it */
.section-title:has(+ .section-subtitle) {
  margin-bottom: 0.5rem;
}

.section-subtitle {
  text-align: center;
  font: var(--type-body-lg);
  text-wrap: balance;
  color: var(--text-muted);
  margin-bottom: var(--title-gap);
}

.section-dark .section-subtitle {
  color: #9aa0a8;
}

/* BUTTONS
   Every button on the site, as Our Process first drew them: an outline,
   square (--radius), a fingertip tall (--btn-h), with its words in
   capitals; dark on a light ground and white on a dark one. Only two are
   filled (.btn-primary): the hero's Get a Quote, in red, and the managed
   card's Enquire, in its graphite. .btn-icon squares one round an icon on
   its own. A hover turns the outline red and
   never lifts it: nothing on the site lifts or casts a shadow. A tap on a
   phone lays no grey wash over it either: what the button does shows the
   tap. Greyed with aria-disabled rather
   than disabled where one runs out, so it keeps the keyboard's focus. */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.625rem;
  min-height: var(--btn-h);
  padding: 0 var(--btn-px);
  border: 1px solid var(--outline-on-light);
  border-radius: var(--radius);
  --fill: transparent;
  background-color: var(--fill);
  color: var(--text-dark);
  font: var(--type-label-sm);
  letter-spacing: var(--letter-spacing-wide);
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: var(--transition-fast);
}

/* on the dark grounds, the hero's photo among them */
:is(.section-dark, .hero, .footer) .btn {
  border-color: var(--outline-on-dark);
  --fill: transparent;
  color: var(--text-white);
}

.btn .icon {
  flex: none;
}

/* An icon beside words is one line of them tall (the words' own size, 1lh
   of it), so on one line the two stand centred on each other, and where
   the words take two lines it stands level with the first (Alban, 27
   September 2026: at 18.2px it was taller than their 17.5px line). */
.btn:not(.btn-icon) .icon {
  font-size: inherit;
  height: 1lh;
}

.btn-icon {
  width: var(--btn-h);
  padding: 0;
}

.btn-icon .icon {
  font-size: 1rem;
}

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

.btn.btn-primary {
  border-color: var(--accent);
  --fill: var(--accent);
  color: var(--text-white);
}

@media (hover: hover) {
  .btn:not([aria-disabled="true"], :disabled):hover {
    border-color: var(--accent);
  }

  :is(.section-dark, .hero, .footer) .btn:not([aria-disabled="true"], :disabled):hover {
    border-color: var(--accent-on-dark);
  }

  .btn.btn-primary:not([aria-disabled="true"], :disabled):hover {
    border-color: var(--accent-dark);
    --fill: var(--accent-dark);
  }
}

/* Grid System */
.grid {
  display: grid;
  gap: var(--card-gap);
}

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

@media (max-width: 640px) {
  .grid-2 {
    grid-template-columns: 1fr;
  }
}

/* ==========================================
   NAVIGATION HEADER
   ========================================== */
/* A white bar pinned to the top of the screen on every page, over the
   hero's photo too, with the light ground's outline along its foot and its
   words and icons dark. Scrolling down slides it up out of the way, and
   scrolling back up brings it back (script.js sets .is-tucked, after a
   good run of scrolling each way).
   Safari, on an iPhone and a Mac, fills the strip behind the status bar
   (and the band around its floating controls) with the colour of whatever
   is pinned across the top of the screen: WebKit hit-tests one point 4px
   below the top, takes the first fixed or sticky ancestor at least 90% of
   the screen's width, over 10px tall and not near-transparent, and reads
   its background-color from its style (LocalFrameView::fixedContainerEdges).
   The fill lasts only while that point finds something: slid out of the
   screen, the strip shows Safari's own blur of what lies behind the status
   bar, the tucked header and the window's colour above it, which over a
   dark section reads as a dark fade (an iPhone, 25 September 2026; WebKit's
   source says it keeps the last colour, and the phone doesn't). So in
   Safari on an iPhone the tucked header keeps 6px of its foot in the
   screen, with its outline along it (below): WebKit finds it there as it
   finds the whole bar, the same white, and the strip stays solid white
   throughout, with nothing to fade. The white is on the header itself,
   read from its style rather than sampled from pixels, and nothing else
   pinned full-width may sit at the top of the screen. */
.header {
  position: fixed;
  top: 0;
  left: 0;
  /* gives back the scrollbar's width itself while the page is frozen (see
     body) */
  width: calc(100% - var(--lock-gutter));
  height: var(--header-h);
  /* its bar runs up under the status bar, its logo and menu below it */
  padding-top: env(safe-area-inset-top);
  display: flex;
  align-items: center;
  z-index: 100;
  background-color: var(--bg-white);
  border-bottom: 1px solid var(--outline-on-light);
  /* the slide takes the even curve: --ease-out leaps a third of the way in
     its first frame, and the header jumped */
  transition: translate 0.4s var(--ease-even);
}

/* Tucked away above the screen. It keeps the strip under the status bar
   itself where there is one (the site opened from an iPhone's home
   screen), so the time and battery never sit on the page. It never goes
   while the menu is open, and comes back while the keyboard's focus is in
   it. */
.header.is-tucked:not(.nav-open, :has(:focus-visible)) {
  translate: 0 calc(-100% + env(safe-area-inset-top));
}

/* In Safari on an iPhone (script.js sets html.safari-iphone), all but 6px
   of its foot, with its outline along it: a thin strip under the status
   bar, which reads as the bar folded up and keeps Safari's white fill
   behind the status bar (see above). Nowhere else: a browser with a bar of
   its own above the page (Chrome on an iPhone, any browser on a computer)
   would show the strip on the page for nothing. */
html.safari-iphone .header.is-tucked:not(.nav-open, :has(:focus-visible)) {
  translate: 0 calc(-100% + env(safe-area-inset-top) + 6px);
}

@media (prefers-reduced-motion: reduce) {
  .header {
    transition: none;
  }
}

/* The menu opens out of the header (the drawer, below): the bar stays, with
   the logo and the toggle, which becomes the cross. */

/* The veil behind the open menu: over the page, under the header and its
   drawer (100). A tap on it closes the menu (script.js). Light enough for
   the page to show through, since the drawer is white and would lose its
   edge against a black page; dark rather than white, so the drawer still
   stands off the page.
   It lies on the page, the whole of it, rather than being pinned to the
   screen. iPhone Safari draws the page on under its floating controls, but
   nothing pinned: a pinned dimmer would stop at their top, over a bright
   strip of page. And Safari looks for something pinned at the middle of
   the screen's foot, and paints it as a solid bar under the controls in
   place of the page, keeping it there for as long as it is visible; so
   shut, the dimmer is hidden, not only see-through. */
body::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 98;
  background: rgba(16, 18, 22, 0.45);
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  /* on the menu's clock; hidden once it has faded */
  transition: opacity var(--nav-move) var(--ease-even),
              visibility 0s linear var(--nav-move);
}

body:has(.header.nav-open)::after {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition: opacity var(--nav-move) var(--ease-even);
}

.header .container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
}

/* Brand lockup: the real AB monogram (black + red) on a white tile with
   the light ground's outline, and "Alb Homes" beside it, dark on the
   header's white bar. The tile is sized from --mark-h, the monogram's
   height: it stands half as tall again as the monogram, and sits half a
   monogram's height from the wordmark, which is set at the monogram's
   size, in the logo's own weight (--font-weight-logo in :root). */
.logo {
  --mark-h: 26px;
  display: flex;
  align-items: center;
  gap: calc(var(--mark-h) * 0.5);
  color: var(--text-dark);
}

.logo-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-white);
  border: 1px solid var(--outline-on-light);
  border-radius: var(--radius);
  padding: calc(var(--mark-h) * 0.2) calc(var(--mark-h) * 0.3);
  flex: none;
}

/* The crop window, the left third of the picture: the AB monogram. Its
   width is set outright, not as an aspect-ratio, which Safari leaves out
   when it sizes the tile around it: the tile came out as wide as the whole
   picture. */
.logo-mark {
  height: var(--mark-h);
  width: calc(var(--mark-h) * 333 / 209);
  overflow: hidden;
  display: block;
}

.logo-mark img {
  height: 100%;
  width: auto;
  max-width: none;           /* override the global img { max-width:100% } */
  display: block;
}

/* the wordmark: part of the logo, so set apart from the type styles, but
   from the same scale, in the logo's own weight (:root) */
.logo-word {
  font-family: var(--font-heading);
  font-weight: var(--font-weight-logo);
  font-size: var(--font-size-2xl);
  line-height: var(--line-height-none);
  color: var(--text-dark);
}

/* the slimmer bar where the menu is a button: the pair a step smaller */
@media (max-width: 1024px) {
  .logo {
    --mark-h: 22px;
  }

  .logo-word {
    font-size: var(--font-size-xl);
  }
}

/* Navigation Links */
/* The list's wrapper is nothing on a desktop, where the list is a row in
   the bar; on a phone it is the drawer the list unfolds in (below). */
.nav-drawer {
  display: contents;
}

/* On a desktop, the links in a row to the right, evenly spaced, the last
   one on the page's right edge. Contact is one of them, like the rest. */
.nav-menu {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: clamp(1.75rem, 3.2vw, 3rem);
  flex: 1;
  margin: 0 0 0 2.5rem;
  list-style: none;
}

/* labels, a weight lighter than a button's: dark on the white bar, with
   the brand red and an underline for the hover */
.nav-link {
  font: var(--type-label-sm);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--letter-spacing-wide);
  color: var(--text);
  position: relative;
  padding: 0.5rem 0;
}

/* the underline only moves, so it takes the movement curve alone: with the
   colour fades in its list too, it re-ran the link's colour fade of its
   own on every frame, as its colour is inherited from the link */
.nav-link::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 2px;
  background-color: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s var(--ease-out);
}

@media (hover: hover) {
  .nav-link:hover {
    color: var(--accent);
  }

  .nav-link:hover::after {
    transform: scaleX(1);
  }
}

/* Mobile Toggle */
/* A <button>, so it's reachable by keyboard and announces its open state;
   the browser's own button styling is cleared here. */
.mobile-toggle {
  display: none;
  position: relative;      /* the two icons are placed against this box */
  width: 1.5em;
  height: 1.5em;
  padding: 0;
  border: none;
  background: none;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--text-dark);
  cursor: pointer;
}

/* The drawing is 24px, the button a finger's 48: its reach runs past the
   drawing on every side (the bar is 60px tall, so it stays in the bar). */
.mobile-toggle::before {
  content: "";
  position: absolute;
  inset: -12px;
}

/* Both icons share the button and cross-fade with a quarter-turn, so the
   bars seem to turn into the cross. */
.mobile-toggle .icon {
  position: absolute;
  inset: 0;
  margin: auto;
  transition: opacity var(--nav-move) var(--ease-even),
              transform var(--nav-move) var(--ease-even);
}

/* closed: the bars are the button */
.mobile-toggle .icon-menu-2 { opacity: 1; transform: rotate(0deg); }
.mobile-toggle .icon-x { opacity: 0; transform: rotate(-90deg); }

/* open: they trade places, each turning the same way */
.header.nav-open .mobile-toggle .icon-menu-2 { opacity: 0; transform: rotate(90deg); }
.header.nav-open .mobile-toggle .icon-x { opacity: 1; transform: rotate(0deg); }

/* Until the page has drawn twice (script.js then sets .nav-ready), none of
   the menu moves. Shut is where it starts, and a browser that works out
   its style once before the phone's layout applies (a first paint can)
   would otherwise play it shutting: sliding up, the cross turning to bars.
   Not until the first tap, though: a browser starts no transition where
   the style before the change had none, so that tap snapped. */
.header:not(.nav-ready) :is(.nav-drawer, .nav-menu, .mobile-toggle .icon),
body:has(.header:not(.nav-ready))::after {
  transition: none !important;
}

@media (max-width: 1024px) {
  /* The drawer: the header itself opening out. It hangs from the foot of
     the bar (the header is fixed, so top: 100% is the bar's foot), white
     like the bar with the outline along its foot, and unfolds from nothing
     to the list's height as a grid row (0fr to 1fr) on the even curve, the
     outline riding its edge; open, it covers the bar's own outline, so bar
     and drawer read as one box, with the logo and the cross in the bar
     above. The words are held back a moment while it unfolds and fade in
     slowly (below). Shutting, the words go quickly while it folds up at
     once; hidden once it has, so that shut it is out of reach of Tab and
     screen readers, not only out of sight. Adding .nav-open to the header
     plays the opening and removing it the closing. */
  .nav-drawer {
    display: grid;
    grid-template-rows: 0fr;
    position: absolute;
    top: 100%;
    left: 0;
    width: 100%;
    background: var(--bg-white);
    border-bottom: 1px solid var(--outline-on-light);
    visibility: hidden;
    transition: grid-template-rows var(--nav-move) var(--ease-even),
                visibility 0s linear var(--nav-move);
  }

  .header.nav-open .nav-drawer {
    grid-template-rows: 1fr;
    visibility: visible;
    transition: grid-template-rows var(--nav-move) var(--ease-even),
                visibility 0s;
  }

  /* The list, clipped by the unfolding row, and scrolling within the
     drawer if the screen is too short for all of it (a phone on its side).
     Its padding lines the links up with the logo (see .container). No
     backdrop-filter: re-blurring the screen every frame made an earlier
     panel's slide stutter on a phone. */
  .nav-menu {
    min-height: 0;
    max-height: calc(100svh - var(--header-h));
    overflow-y: auto;
    overscroll-behavior: contain;
    flex-direction: column;
    /* from the top, not packed to the end as in the bar's row: packed to
       the end, a list too tall for the screen overflowed upwards, where
       no scroll reaches */
    justify-content: flex-start;
    align-items: stretch;
    gap: 0;
    margin: 0;
    /* no padding of its own: padding would hold the folded row open by
       that much, and the fold would end with a jump; the room is the
       links' (below) */
    padding: 0;
    opacity: 0;
    /* shutting: the words go first, quickly */
    transition: opacity var(--nav-fade-out) ease-in-out;
  }

  .header.nav-open .nav-menu {
    opacity: 1;
    /* opening: a moment after the header starts to open, they fade in
       slowly */
    transition: opacity var(--nav-fade) ease-out var(--nav-pause);
  }

  /* the links carry the drawer's room: the safe areas at the sides, lined
     up with the logo (see .container), a little above the first and more
     below the last */
  .nav-menu .nav-link {
    display: block;
    padding: 1.15rem max(var(--gutter), env(safe-area-inset-right)) 1.15rem max(var(--gutter), env(safe-area-inset-left));
    font: var(--type-heading-2xl);
    letter-spacing: normal;
    color: var(--text-dark);
  }

  .nav-menu li:first-child .nav-link {
    padding-top: calc(1.15rem + 0.25rem);
  }

  .nav-menu li:last-child .nav-link {
    padding-bottom: calc(1.15rem + 1.5rem);
  }

  /* the desktop underline has no job in a stacked list */
  .nav-menu .nav-link::after {
    display: none;
  }

  .nav-menu .nav-link:active {
    color: var(--text-dark);
    opacity: 0.55;
  }

  /* below 1024px the menu button replaces the desktop bar */
  .mobile-toggle {
    display: block;
  }

  /* reduced motion: every part of the menu at once */
  @media (prefers-reduced-motion: reduce) {
    .nav-drawer,
    .header.nav-open .nav-drawer,
    .nav-menu,
    .header.nav-open .nav-menu,
    body::after,
    body:has(.header.nav-open)::after,
    .mobile-toggle .icon {
      transition: none;
    }
  }
}

/* ==========================================
   HERO BANNER
   ========================================== */
/* lvh is the screen's height with the browser's toolbars tucked away, svh
   with them showing. The photo fills the first and the strip under the
   controls below it (--under-controls), so on an iPhone it runs on under
   Safari's floating controls down to the foot of the screen. The words
   keep to the second, the room the padding at the foot leaves them, so no
   toolbar covers the facts. The vh line is for browsers without either. */
.hero {
  /* the screen below the large viewport, which only iPhone Safari keeps
     (below) */
  --under-controls: 0px;
  min-height: 100vh;
  min-height: calc(100lvh + var(--under-controls));
  /* the content sits in the space below the header, not behind it */
  padding-top: var(--header-h);
  padding-bottom: calc(100lvh - 100svh + var(--under-controls));
  position: relative;
  /* its layers stack among themselves, under the section that follows */
  isolation: isolate;
  background-color: var(--primary);
  display: flex;
  flex-direction: column;
  /* cut off at the sides */
  overflow-x: clip;
  color: var(--text-white);
}

/* iPhone Safari (and any iOS browser: -webkit-touch-callout is theirs
   alone) keeps a strip below even the large viewport, under its tucked-away
   controls and the home bar, 58pt on an iPhone 17 Pro, and draws the page
   on under it. The photo covers it, with a little to spare. */
@supports (-webkit-touch-callout: none) {
  .hero {
    --under-controls: 4rem;
  }
}

.hero-bg {
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* Dark enough for the heading to be read first, light enough for the house
   to be the subject: with the overlay, about a fifth of the light gets
   through. */
.hero-bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  filter: brightness(0.78) contrast(1.05);
}

/* The shade over the photo, in the photo's box (index.html): darkest
   behind the words in the middle, and again at the foot, under the facts */
.hero-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background:
    radial-gradient(ellipse 60% 55% at 50% 45%, rgba(8, 10, 13, 0.42), transparent),
    linear-gradient(to bottom, rgba(8, 10, 13, 0.3), rgba(8, 10, 13, 0.4) 60%, rgba(8, 10, 13, 0.7));
}

/* The title and buttons centred in the space the facts leave above them */
.hero .container {
  position: relative;
  z-index: 3;
  flex: 1;
  display: flex;
  flex-direction: column;
}

.hero-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding-block: 4rem;
}

.hero-title {
  font: var(--type-hero);
  letter-spacing: var(--letter-spacing-display);
  text-transform: uppercase;
  margin-bottom: 2.5rem;
  color: var(--text-white); /* Force bright white over the dark hero overlay */
  animation: fadeInUp 1s 0.2s both;
}

.hero-title span {
  color: inherit;
}

/* the outlined button over the photo takes a firmer outline to show on it */
.hero-actions .btn:not(.btn-primary) {
  border-color: rgba(255, 255, 255, 0.6);
}

/* The 2px of padding keeps the buttons' outlines off the group's edge:
   while the group fades in, WebKit draws it as a layer of its own, sized
   to whole pixels, and with the group at a fraction (the title's size is
   a share of the screen's width) the layer's snapped edge fell inside the
   last button's 1px outline, so View Portfolio had no foot for the whole
   entrance on an iPhone (26 September 2026). */
.hero-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 2px;
  animation: fadeInUp 1s 0.6s both;
}

@media (max-width: 640px) {
  .hero-actions {
    flex-direction: column;
    width: 100%;
  }
  .hero-actions .btn {
    width: 100%;
  }
}

/* ==========================================
   THE HERO'S FACTS
   A line of white icons and words drifting leftwards along the foot of the
   hero, like a row of clients' logos. script.js moves each fact on its own,
   round a loop the length of the line (see there why). Each fact carries
   the space after it, so the gaps stay even all the way round. The ends
   fade out rather than cut off.
   ========================================== */
.hero-facts {
  overflow: hidden;
  padding-block: 2rem 2.5rem;
  -webkit-mask-image: linear-gradient(to right, transparent, #000 10%, #000 90%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 10%, #000 90%, transparent);
  animation: fadeInUp 1s 0.8s backwards;
}

.hero-facts-track {
  display: flex;
  width: max-content;
}

.hero-fact {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-inline-end: 4.5rem;
  white-space: nowrap;
  font: var(--type-label-sm);
  letter-spacing: var(--letter-spacing-caps);
  text-transform: uppercase;
  color: var(--text-white);
}

.hero-fact .icon {
  font-size: 1.75rem;
  margin-inline-end: 0.25rem;
}

.hero-fact strong {
  font: var(--type-heading-xl);
  letter-spacing: var(--letter-spacing-wide);
}

@media (max-width: 640px) {
  .hero-facts {
    padding-block: 1.5rem 2rem;
  }

  .hero-fact {
    padding-inline-end: 3rem;
  }
}

/* Asked for less movement, the facts stand still, wrapped and centred
   (script.js doesn't move them), and the hero arrives without its rise. */
@media (prefers-reduced-motion: reduce) {
  .hero-title,
  .hero-actions,
  .hero-facts {
    animation: none;
  }

  .hero-facts {
    -webkit-mask-image: none;
    mask-image: none;
  }

  .hero-facts-track {
    width: auto;
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 1rem;
  }

  .hero-fact {
    padding-inline: 1.5rem;
  }
}

/* Animations */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ==========================================
   SERVICES SECTION
   ========================================== */
.service-card {
  background-color: transparent;
  border-radius: var(--radius);
  padding: var(--card-pad);
  border: 1px solid var(--outline-on-light);
  position: relative;
  overflow: hidden;
}


/* The tile fills in as --fill (see the tokens), which fades; a fade of the
   background-color itself flashed the tile's old colour on its last frame.
   It stands 2rem over the title's capitals (.service-title), as the
   managed card's tile does over its title's (Alban, 27 September 2026:
   36px, 34px and 32px over the title's line all read wide). */
.service-icon {
  width: 70px;
  height: 70px;
  border-radius: var(--radius);
  --fill: var(--bg-soft);
  background-color: var(--fill);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--primary);
  margin-bottom: 2rem;
  transition: color var(--hover-fade), --fill var(--hover-fade), border-color var(--hover-fade);
}

@media (hover: hover) {
  .service-card:hover .service-icon {
    --fill: var(--accent);
    color: var(--text-white);
  }
}


.service-card.in-view .service-icon {
  --fill: var(--accent);
  color: var(--text-white);
}

/* Its line's room above the capitals (7px of Outfit's at this size) is
   trimmed off, so the tile's 2rem runs to the letters themselves, as it
   does on the managed card, whose Anton title has next to none (Alban, 27
   September 2026: 32px to the line read as 39px). A browser that can't
   trim shows the room, as before. */
.service-title {
  font: var(--type-heading-2xl);
  margin-bottom: 1rem;
  text-box: trim-start cap alphabetic;
}

.service-desc {
  color: var(--text-muted);
  font: var(--type-body-md);
}

.service-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font: var(--type-body-sm);
  font-weight: var(--font-weight-medium);
  color: var(--text-dark);
}

.service-item .icon {
  color: var(--accent);
  font-size: 0.85rem;
}

/* The drifting list's window. Its edges are faded with a mask, so the
   points fade in and out as they drift rather than being sliced off; the
   top fade sits in the padding, clear of the first point at rest. */
.service-rotator {
  --fade-top: 1rem;
  --fade-bottom: 28px;
  position: relative;
  margin-top: 0.75rem;
  padding-top: var(--fade-top);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent);
}

.service-rotator-track {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  will-change: transform;
}

/* Every point is in the HTML for search engines, but at rest the window
   shows three: points 4 on are parked just below it (the track's
   will-change makes it their containing block), so the window is exactly
   three points tall at any width. script.js adds .is-built when the list
   first moves. */
.service-rotator:not(.is-built) .service-item:nth-child(n+4) {
  position: absolute;
  top: 100%;
}

/* Asked for less movement, the list doesn't drift (script.js), so every
   point stands in the card, unmasked. */
@media (prefers-reduced-motion: reduce) {
  .service-rotator {
    -webkit-mask-image: none;
    mask-image: none;
  }

  .service-rotator:not(.is-built) .service-item:nth-child(n+4) {
    position: static;
  }
}

/* ==========================================
   FULLY MANAGED PACKAGE
   The service card in the brand's other palette: warm paper and brass kept
   to hairlines. A brass fill reads as olive, so solid areas are graphite.
   ========================================== */
/* the managed card follows on from the services grid, one card-gap below
   it, as if it were the grid's last row */
#services {
  padding-bottom: var(--card-gap);
}

.managed {
  padding-top: 0;
}

/* Behaves exactly like a service card (edge, highlight, timing);
   only the palette differs. */
.managed-panel {
  position: relative;
  overflow: hidden;
  padding: var(--card-pad);
  border-radius: var(--radius);
  background-color: var(--paper);
  border: 1px solid var(--outline-on-light);
}


/* Stacked, the four lines stand 30px under the pitch and the button 2rem
   under them (Alban, 27 September 2026: they stood 56px and 52px apart,
   and the lines 21.6px; then 2rem each, which read a hair wider under
   the pitch, whose last line has more room under its letters) */
.managed-head {
  /* left-aligned, so it reads like the service cards above it */
  max-width: 640px;
  margin: 0 0 1.875rem 0;
  text-align: left;
}

/* A tile a shade darker than the card's paper, as the services' grey tile
   is a shade darker than their white */
.managed-icon {
  margin: 0 0 2rem 0;
  --fill: color-mix(in srgb, var(--brass) 8%, var(--paper));
  color: var(--brass);
  transition: color var(--hover-fade), --fill var(--hover-fade), border-color var(--hover-fade);
}

/* highlighting inverts the tile, as on the service cards, in brass */
@media (hover: hover) {
  .managed-panel:hover .managed-icon {
    --fill: var(--brass);
    color: var(--text-white);
  }
}


.managed-panel.in-view .managed-icon {
  --fill: var(--brass);
  color: var(--text-white);
}

.managed-title {
  font: var(--type-display);
  letter-spacing: var(--letter-spacing-display);
  text-transform: uppercase;
  color: var(--text-dark);
  margin-bottom: 1.35rem;
}

.managed-lead {
  font: var(--type-body-lg);
  text-wrap: balance;
  color: var(--text-muted);
}

/* the four things the lead promises; brass ticks on the brass card */
.managed-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1rem;
  /* the width the points measure themselves against (.managed-list-text) */
  container-type: inline-size;
}

.managed-list-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.85rem;
  align-items: start;
}

.managed-list-item > .icon {
  margin-top: 0.15rem;
  font-size: 0.95rem;
  color: var(--brass);
}

/* A point's title with its description beside it where the list is wide
   enough for every point to fit on one line, and under it where it isn't. */
.managed-list-text {
  display: flex;
  flex-direction: column;
}

@container (min-width: 36rem) {
  .managed-list-text {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 0.6rem;
  }
}

.managed-list-title {
  display: block;
  font: var(--type-heading-md);
  color: var(--text-dark);
}

.managed-list-note {
  display: block;
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.managed-foot {
  margin-top: 2rem;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1rem;
  text-align: left;
}


/* From 1025px the pitch and its button take the left, and the four lines
   the right behind a brass rule. Stacked below that. */
/* The filled button (.btn-primary) in graphite rather than red or gold: a
   gold fill this size reads as khaki, and graphite keeps the card's palette
   apart from the red. */
.managed-foot .btn-primary {
  border-color: var(--primary);
  --fill: var(--primary);
}

@media (hover: hover) {
  .managed-foot .btn-primary:not([aria-disabled="true"], :disabled):hover {
    border-color: #000;
    --fill: #000;
  }
}

@media (min-width: 1025px) {
  .managed-panel {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    column-gap: 3.5rem;
    align-items: start;
  }

  .managed-head {
    grid-column: 1;
    grid-row: 1;
    margin-bottom: 0;
  }

  .managed-foot {
    grid-column: 1;
    grid-row: 2;
    margin-top: 2.75rem;
  }

  /* full height, so the rule divides the card; the lines centred along it */
  .managed-list {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: stretch;
    align-content: center;
    margin-top: 2.5rem;
    padding-left: 3.5rem;
    border-left: 1px solid rgba(138, 109, 31, 0.14);
    gap: 1.9rem;
  }
}


@media (max-width: 560px) {

  .managed-foot .btn {
    width: 100%;
  }
}

/* A button whose words are longer than a phone's card is wide (Enquire
   about this service up to 360px, WhatsApp us photos at 320px) takes a
   second line rather than running out of the card. */
@media (max-width: 560px) {
  .managed-foot .btn,
  .review-actions .btn {
    white-space: normal;
    text-align: center;
    padding-block: 0.625rem;
  }

  /* Words after an icon (the thank-you's and the fallback's buttons) set
     from the icon's side, and the icon stands level with their first
     line: the two are a row, each from its top, and the row is centred
     in the button's height, as a single line is. Centred, the words
     stood off in the middle of the button, with the icon alone at its
     edge between their two lines (Alban, 27 September 2026). */
  .review-actions .btn {
    display: inline-grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    align-content: center;
    text-align: left;
  }
}

/* ==========================================
   THE QUOTE FORM (#estimator)
   One card. Down the left, the plan: a numbered row per question, its
   name and its answers in a line under it, which is the progress and the
   running summary in one. On the right, one question at a time. Before
   sending, the summary takes the card: every answer named by its term,
   in groups, each line a way back to its question. Below 900px the plan
   gives way to a hairline of progress along the card's top edge and an
   answers drawer at the top of the question side. Everything a change
   moves keeps one clock, --q-move, on --ease-out, as Our Process's card
   does, and every change of the card's height is the card's own move
   (script.js eases it): nothing inside animates its height. script.js
   runs it.
   ========================================== */
/* An outline on the section, like Our Process's card (--outline-on-dark):
   no fill or shadow lifting it off the page */
.quote {
  position: relative;
  border: 1px solid var(--outline-on-dark);
  border-radius: var(--radius);
  /* clip, not hidden: hidden would make the card a scroller */
  overflow: clip;
  /* the form's own greys, and its hairline */
  --q-line: rgba(255, 255, 255, 0.08);
  --q-soft: #d2d5da;
  --q-muted: #9aa0a8;
  /* at least 4.5:1 on the ground for the small labels in --q-dim (Edit,
     Optional, the numbers), and 3:1 for the big figures in --q-faint */
  --q-dim: #7d838c;
  --q-faint: #5c616a;
  /* the one clock for everything a change moves, with --ease-out: a
     question giving way to the next, a follow-up opening or shutting,
     the card's height, the plan's rows and the summary taking the card
     (script.js reads it too) */
  --q-move: var(--move);
}

/* While it first draws, nothing animates into place. */
.quote.is-instant,
.quote.is-instant * {
  transition: none !important;
}

.quote-grid {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  grid-template-areas: "rail pane";
  --rail-w: 340px;
}

@media (max-width: 1099px) {
  .quote-grid {
    --rail-w: 296px;
  }
}

/* ---- The plan (left) ---- */
.quote-rail {
  grid-area: rail;
  position: relative;
  min-height: 0;
  border-right: 1px solid var(--outline-on-dark);
}

/* The plan scrolls inside the card rather than stretching it: the card is as
   tall as the question side, so Back and Next never move. Only a long route
   needs to, once its answers fill in (two or three kinds of job at once, or
   a renovation); script.js keeps the current row in sight and fades
   whichever edge has more beyond it. The scroller fills the plan's side,
   with the room above and below the rows (the same both ways) inside it,
   so rows scroll right up to the card's edge; a scroll that runs out
   carries on down the page. The first row's name sits level with the
   question side's count (the sheet's padding, below). */
.rail-scroll {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  /* no scrollbar: the faded edges say there's more, and a bar flashing up
     beside the rows as they move meant nothing */
  scrollbar-width: none;
  --fade-top: 0px;
  --fade-bottom: 0px;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent);
}

.rail-scroll::-webkit-scrollbar {
  display: none;
}

/* The room above and below the rows, inside the scroll: a little more
   above, so the first row's name sits level with the question side's
   count, centred in its 40px row under the same 56px of padding. */
.rail-sheet {
  --rail-pad-top: 51px;
  --rail-pad-bottom: 44px;
  position: relative;
  padding: var(--rail-pad-top) 32px var(--rail-pad-bottom);
}

/* The tint behind the current row. One marker for the whole plan, which
   script.js moves (transform and height), so it travels from row to row
   rather than blinking from one to the next, and grows with the row's
   answer line on the same clock. */
.rail-marker {
  position: absolute;
  /* level with the list's top, which script.js measures the rows from */
  top: var(--rail-pad-top);
  left: 16px;
  right: 16px;
  height: 0;
  border-radius: var(--radius);
  background: rgba(var(--accent-rgb), 0.14);
  pointer-events: none;
}

.rail-list {
  position: relative;
  z-index: 1;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A row opens and closes as the route gains or loses its question, on
   the form's one clock. */
.rail-item {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--q-move) var(--ease-out), opacity var(--q-move) var(--ease-out);
}

.rail-item.is-out {
  grid-template-rows: 0fr;
  opacity: 0;
}

/* The row reaches 16px into the plan's padding each side, so its tint does. */
.rail-clip {
  min-height: 0;
  margin: 0 -16px;
  overflow: hidden;
}

/* A row: its number, and beside it the step's block (its name, and its
   answer line under it), the number centred on the name's line */
.rail-row {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  column-gap: 12px;
  align-items: start;
  width: 100%;
  margin: 1px 0;
  padding: 14px 16px;
  border-radius: var(--radius);
  text-align: left;
  --fill: transparent;
  background-color: var(--fill);
  color: var(--text-white);
  cursor: pointer;
  transition: --fill var(--hover-fade), opacity var(--q-move) var(--ease-out);
}

/* inside the row: the clip around it would cut a ring drawn outside */
.rail-row:focus-visible {
  outline-offset: -2px;
}

/* The number, small capitals like Our Process's, red on the question on
   screen. Its line box is the name's height (12px at 1.7 is 20.4px, the
   name's 20px), so it sits centred on the name's line rather than on its
   baseline. */
.rail-num {
  font: var(--type-label-xs);
  line-height: var(--line-height-relaxed);
  letter-spacing: var(--letter-spacing-caps);
  color: var(--q-dim);
  transition: color var(--hover-fade), opacity var(--q-move) var(--ease-out);
}

/* THE STEP'S BLOCK: its name, and under it its answers in one muted
   line, the step's own first and each follow-up's after it, joined by a
   middle dot (the summary names each by its term). The line opens on the
   one clock as the first answer is given, and script.js glides the marker
   to the row's finished height on the same clock, so the block grows with
   the line; it closes, keeping its words, if the answer is taken back.
   The plan's rows and the phone's drawer set it in the same styles. */
.plan-body {
  display: block;
  min-width: 0;
}

.plan-label {
  display: block;
  margin: 0;
  font: var(--type-label-md);
  color: var(--q-soft);
  transition: color var(--hover-fade);
}

.plan-answer {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--q-move) var(--ease-out);
}

.plan-answer.is-shown {
  grid-template-rows: 1fr;
}

.plan-answer-clip {
  display: block;
  min-height: 0;
  overflow: hidden;
}

.plan-answer-text {
  display: block;
  padding-top: 5px;
  font: var(--type-body-sm);
  color: var(--q-muted);
  overflow-wrap: anywhere;
  opacity: 0;
  transition: opacity var(--q-move) var(--ease-out);
}

.plan-answer.is-shown .plan-answer-text {
  opacity: 1;
}

/* The question on screen */
.rail-row[aria-current="step"] {
  cursor: default;
}

.rail-row[aria-current="step"] .rail-num {
  color: var(--accent-on-dark);
}

.rail-row[aria-current="step"] .plan-label {
  color: var(--text-white);
}

/* An answered question within reach: a way back to it */
@media (hover: hover) {
  .rail-row:not([aria-current]):not(:disabled):hover {
    --fill: rgba(255, 255, 255, 0.04);
  }

  .rail-row:not([aria-current]):not(:disabled):hover .plan-label {
    color: var(--text-white);
  }
}

/* Still to come: there for the shape of the route, not a way to skip ahead */
.rail-row:disabled {
  opacity: 0.45;
  cursor: default;
}
/* ---- The question side (right) ---- */
.quote-pane {
  grid-area: pane;
  /* the questions size their tiles and fields to this side's width */
  container: pane / inline-size;
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  /* tall enough for a slider question, so Back and Next stay where they are
     from one question to the next (only a title that wraps pushes them) */
  min-height: 696px;
  padding: var(--pane-pt) var(--pane-px) var(--pane-pb);
  --pane-pt: 56px;
  --pane-px: 64px;
  --pane-pb: 56px;
  /* the buttons' height, which "Not sure yet" lines up with */
  --bar-h: var(--btn-h);
}

@media (max-width: 1199px) {
  .quote-pane {
    --pane-px: 44px;
  }
}

/* Phones only: the hairline of progress (below) */
.quote-progress {
  display: none;
}

/* Where you are, and on a phone the answers button beside it: a 40px
   row with the count centred in it, level with the plan's first row */
.quote-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 40px;
  margin-bottom: 32px;
}

/* "04 / 09": where you are, of how many this route asks */
.quote-count {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin: 0;
  font: var(--type-numeral-2xl);
  color: var(--accent-on-dark);
}

.count-total {
  color: var(--q-faint);
}

/* A number that changes rolls to the next (script.js): the old one slides
   away over the new, laid on top of it. */
.count-now,
#countTotal,
.rail-num {
  position: relative;
  display: inline-block;
}

.roll {
  display: inline-block;
}

.roll.is-gone {
  position: absolute;
  top: 0;
  left: 0;
}

/* Phones only: the answers so far, in a drawer */
.answers-toggle,
.answers-drawer {
  display: none;
}

/* ---- The questions ---- */
/* All of them share one cell; only the one on screen is shown (and, for a
   moment, the one it replaces, on its way out). Neither is positioned:
   whatever leaves the flow (a question, a follow-up) holds its place
   against the question side itself, which is, at the offset script.js
   reads off it, as "Not sure yet" sits against its foot. A question out
   of the flow is positioned, so the "Not sure yet" going with it is held
   where it stood by script.js (pinUnsure). */
.quote-stage {
  display: grid;
  margin-bottom: 32px;
}

.q-step {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.q-step[hidden] {
  display: none;
}

/* The question on its way out fades over the one arriving, out of the
   flow where it stood (script.js sets its top), so the card takes the
   new question's height at once (script.js eases it there), as Our
   Process's card does. */
.q-step.is-leaving {
  position: absolute;
  left: var(--pane-px);
  right: var(--pane-px);
  pointer-events: none;
}

.q-step > * + * {
  margin-top: 32px;
}

/* a follow-up carries its own gap (below), so a shut one leaves none */
.q-step > .q-reveal {
  margin-top: 0;
}

.q-head {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* The question, set large: here it is the only thing being asked. It takes
   the focus when a jump lands on it, so a screen reader reads it; it isn't a
   control, so no ring. */
.quote .q-title {
  margin: 0;
  font: var(--type-subhead);
  color: var(--text-white);
  text-wrap: balance;
  outline: none;
}

/* the ? after the head-height question's last word, and its "floor-to-ridge"
   kept to one line rather than broken at a hyphen */
.q-title .help-dot {
  margin-left: 12px;
  vertical-align: middle;
}

.q-title .nobr {
  white-space: nowrap;
}

.q-hint {
  margin: 0;
  font: var(--type-body-md);
  color: var(--q-muted);
}

/* Which job a question belongs to, over it in small red capitals like
   Our Process's tag, once more than one kind of work is chosen
   (script.js): each job's questions come in turn, and without it the
   thread is lost. */
.q-tag {
  margin: 0;
  font: var(--type-label-xs);
  letter-spacing: var(--letter-spacing-caps);
  text-transform: uppercase;
  color: var(--accent-on-dark);
}

.q-tag[hidden] {
  display: none;
}

/* TILES: the project type (four across when there's room), and the pairs
   that carry a line under their title */
.q-tiles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* every row as tall as the tallest, so all the tiles match */
  grid-auto-rows: 1fr;
  gap: 14px;
}

@container pane (min-width: 560px) {
  .q-tiles-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.q-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 28px 16px;
  border: 1px solid var(--outline-on-dark);
  border-radius: var(--radius);
  --fill: transparent;
  background-color: var(--fill);
  color: var(--text-white);
  text-align: center;
  cursor: pointer;
  transition: --fill var(--hover-fade), border-color var(--hover-fade), scale 0.2s var(--ease-out);
}


.q-tile-title {
  font: var(--type-label-lg);
}

.q-tile-note {
  font: var(--type-body-sm);
  color: var(--q-muted);
  transition: color var(--hover-fade);
}

/* the hover leaves a chosen tile as it is */
@media (hover: hover) {
  .q-tile:hover:not(.is-on) {
    border-color: rgba(var(--accent-on-dark-rgb), 0.5);
  }

}

/* A chosen answer: its red tint, and its outline in the lighter red, as
   every red on the dark ground is: --accent measured 2.2:1 against it,
   and colour-blind visitors couldn't tell a chosen answer from the rest. */
.q-tile.is-on {
  --fill: rgba(var(--accent-rgb), 0.1);
  border-color: var(--accent-on-dark);
}


.q-tile.is-on .q-tile-note {
  color: var(--q-soft);
}

/* a press gives a little under the finger */
.q-tile:active,
.q-pill:active,
.q-option:active {
  scale: 0.97;
}

/* PILLS: a wrapping row of answers, finger-sized */
.q-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.q-pill {
  min-height: 48px;
  padding: 13px 24px;
  border: 1px solid var(--outline-on-dark);
  border-radius: var(--radius);
  --fill: transparent;
  background-color: var(--fill);
  color: var(--text-white);
  font: var(--type-label-md);
  cursor: pointer;
  transition: --fill var(--hover-fade), border-color var(--hover-fade), scale 0.2s var(--ease-out);
}

@media (hover: hover) {
  .q-pill:hover:not(.is-on) {
    border-color: rgba(var(--accent-on-dark-rgb), 0.5);
  }
}

.q-pill.is-on {
  --fill: rgba(var(--accent-rgb), 0.15);
  border-color: var(--accent-on-dark);
}

/* only the answers this job can have are offered (a bungalow's types, a
   new build's budgets) */
.q-pill[hidden] {
  display: none;
}

.q-pills-sm {
  gap: 10px;
}

.q-pills-sm .q-pill {
  min-width: 52px;
  padding: 11px 20px;
}

/* COUNTED OPTIONS (Bathrooms, Bedrooms). The − n + is a box carried on
   the pill's end while the room is on (script.js sets is-on), its outline
   running on from the pill's red one. It comes in at its full width at
   once, fading in, so the row wraps at once and the card eases to fit
   (script.js): nothing but the card animates its size. Going, it holds
   its place on the pill's end (absolute) as it fades, then is put away. */
.pill-count {
  position: relative;
  display: inline-flex;
}

.pill-stepper {
  display: none;
  align-items: stretch;
  border: 1px solid var(--accent-on-dark);
  border-left: 0;
  border-radius: var(--radius);
}

.pill-count.is-on .pill-stepper,
.pill-stepper.is-leaving {
  display: inline-flex;
}

.pill-stepper.is-leaving {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 100%;
  pointer-events: none;
}

/* − and +, and the count between them, in the pill's own type */
.pill-step {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  padding: 0;
  color: var(--text-white);
  font: var(--type-label-lg);
  line-height: var(--line-height-none);
  cursor: pointer;
  transition: color var(--hover-fade);
}

@media (hover: hover) {
  .pill-step:hover {
    color: var(--accent-on-dark);
  }
}

.pill-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  font: var(--type-numeral-md);
  color: var(--text-white);
}

/* A FOLLOW-UP opens under the answer that raises it, at its full height
   at once, its words fading in on the form's one clock while the card
   eases to fit (script.js): the height move is the card's, never the
   follow-up's own. One taken back leaves the flow where it stands
   (absolute against the question side, at the offset script.js reads
   off it) and fades out the same way, so the card goes straight to its
   new height; and one whose
   question changes under an answer (a house's types for a bungalow's)
   fades out as a ghost of itself over the new fading in, as a question
   does. Shut, it takes no room and nothing in it can be reached. */
.q-reveal:not(.is-open, .is-leaving) {
  display: none;
}

.q-step > .q-reveal.is-open {
  margin-top: 40px;
}

.q-reveal.is-leaving {
  position: absolute;
  left: var(--pane-px);
  right: var(--pane-px);
  margin: 0;
  pointer-events: none;
}

/* The follow-up question inside the same step: its title in white like
   the question's, "You can choose more than one" close under it where it
   takes several answers, and its answers 20px below */
.q-sub {
  display: flex;
  flex-direction: column;
}

.q-sub > * + * {
  margin-top: 20px;
}

.q-sub > .q-sub-title + .q-hint {
  margin-top: 5px;
}

.quote .q-sub-title {
  margin: 0;
  font: var(--type-heading-xl);
  color: var(--text-white);
  outline: none;
}

/* A follow-up that's always there (under a slider, or the optional way
   to reach you) stands the same 40px under what's above it. */
.q-step > .q-sub {
  margin-top: 40px;
}

/* "Optional", after a question that can be left */
.q-optional {
  margin-left: 8px;
  font: var(--type-label-xs);
  letter-spacing: var(--letter-spacing-caps);
  text-transform: uppercase;
  color: var(--q-dim);
  vertical-align: middle;
}

/* A quiet line under an answer (the note under "Urgent"), its link in the
   form's softest white, underlined */
.q-aside {
  margin: 0;
  font: var(--type-body-sm);
  color: var(--q-muted);
}

.q-aside a {
  color: var(--q-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--hover-fade);
}

@media (hover: hover) {
  .q-aside a:hover {
    color: var(--text-white);
  }
}

/* the note under "Urgent" keeps close to the answer it's about */
.q-step > .q-reveal-note.is-open {
  margin-top: 16px;
}

/* SOMETHING ELSE: the other kinds of work, in groups under small headings,
   each a small card, two across where there's room. */
.q-groups {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.q-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.q-group-title {
  margin: 0;
  font: var(--type-label-xs);
  letter-spacing: var(--letter-spacing-caps);
  text-transform: uppercase;
  color: var(--q-muted);
}

.q-options {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
}

@container pane (min-width: 560px) {
  .q-options {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.q-option {
  display: flex;
  align-items: center;
  min-height: 56px;
  padding: 14px 16px;
  border: 1px solid var(--outline-on-dark);
  border-radius: var(--radius);
  --fill: transparent;
  background-color: var(--fill);
  color: var(--text-white);
  text-align: left;
  cursor: pointer;
  transition: --fill var(--hover-fade), border-color var(--hover-fade), scale 0.2s var(--ease-out);
}

@media (hover: hover) {
  .q-option:hover:not(.is-on) {
    border-color: rgba(var(--accent-on-dark-rgb), 0.5);
  }
}

.q-option.is-on {
  --fill: rgba(var(--accent-rgb), 0.1);
  border-color: var(--accent-on-dark);
}

.q-option-title {
  font: var(--type-label-md);
}

/* The planning reference, under the two planning answers (its follow-up
   carries the gap above it) */
.q-followup {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 420px;
}

.q-field-label {
  font: var(--type-body-sm);
  font-weight: var(--font-weight-medium);
  color: var(--q-soft);
}

.q-field-hint {
  font: var(--type-body-sm);
  color: var(--q-muted);
}

/* TYPED ANSWERS: body size or more, and never smaller: iPhones zoom the
   page into a field whose text is under 16px */
.q-input {
  width: 100%;
  padding: 18px 20px;
  border: 1px solid var(--outline-on-dark);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-white);
  font: var(--type-body-md);
  transition: border-color var(--hover-fade);
}

.q-input::placeholder {
  color: #8b919a;
}

.q-input:focus {
  border-color: var(--accent-on-dark);
}

.q-input-lg {
  max-width: 520px;
  padding: 20px 22px;
  font-size: var(--font-size-xl);
}

.q-input-ref,
.q-input-postcode {
  text-transform: uppercase;
}

.q-input-ref {
  padding: 16px 18px;
}

.q-input-postcode {
  max-width: 320px;
}

/* the placeholders keep their own case */
.q-input-ref::placeholder,
.q-input-postcode::placeholder {
  text-transform: none;
}

/* Name across the top, phone and email side by side under it (one above the
   other where the side is narrow) */
.q-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  max-width: 720px;
}

.q-input-name {
  grid-column: 1 / -1;
}

@container pane (max-width: 559px) {
  .q-fields {
    grid-template-columns: minmax(0, 1fr);
  }
}

.q-step > .q-fields-hint {
  margin-top: 14px;
  font: var(--type-body-sm);
  color: var(--q-muted);
}

/* What stops a move on (a link in a field, contact details Send would turn
   back), under the fields it is about */
.q-error {
  display: none;
  margin: 0;
  font: var(--type-body-sm);
  color: var(--accent-on-dark);
}

.q-step > .q-error {
  margin-top: 16px;
}

.q-error.show {
  display: block;
}

/* THE SLIDERS. A big reading, and a rail with a line on it. The native range
   input does the work (keyboard, screen readers, the drag) but paints
   nothing; the rail, its fill and the line are drawn beside it from --pct,
   which script.js sets. The input overhangs the rail by half its thumb at
   each end, so the thumb's centre travels exactly the rail's length. */
.q-slider > * + * {
  margin-top: 18px;
}

/* The reading. Dim until the slider is touched: until then it only shows
   where the line starts. It gives way to "Not sure" when that is ticked. */
.q-readout {
  display: grid;
  margin: 0;
}

.q-readout-value,
.q-readout-unsure {
  grid-area: 1 / 1;
  /* the one clock and curve of every change in the card */
  transition: opacity var(--q-move) var(--ease-out), translate var(--q-move) var(--ease-out);
}

.q-readout-value {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

/* The figures at the hero's size, in a tight line box (figures have no
   descenders), so the slider questions fit the card like the rest and Back
   and Next stay put */
.q-readout-num,
.q-readout-unsure {
  font: var(--type-numeral-hero);
  line-height: var(--line-height-display);
  color: var(--q-faint);
  font-variant-numeric: tabular-nums;
}

.q-readout-num {
  transition: color var(--hover-fade);
}

.q-slider.is-touched .q-readout-num {
  color: var(--text-white);
}

/* the unit, as part of the reading */
.q-readout-unit {
  font: var(--type-numeral-3xl);
  color: var(--q-muted);
}

.q-readout-unsure {
  opacity: 0;
  translate: 0 14px;
}

.q-slider.is-unsure .q-readout-value {
  opacity: 0;
  translate: 0 -14px;
}

.q-slider.is-unsure .q-readout-unsure {
  opacity: 1;
  translate: none;
}

.q-track {
  position: relative;
  height: 64px;
  border-radius: var(--radius);
  transition: opacity var(--q-move) var(--ease-out);
  --pct: 0;
}

/* "Not sure yet" sets the slider aside, dimmed; moving it again takes it back */
.q-slider.is-unsure .q-track {
  opacity: 0.35;
}

.q-track-rail,
.q-track-fill {
  position: absolute;
  top: 29px;
  left: 0;
  height: 6px;
  border-radius: var(--radius);
  pointer-events: none;
}

.q-track-rail {
  right: 0;
  background: rgba(255, 255, 255, 0.15);
}

.q-track-fill {
  width: calc(var(--pct) * 100%);
  background: var(--accent);
}

/* The line you see, where the thumb is. Red and breathing until the slider
   is first touched, to invite a drag; white after. */
.q-track-line {
  position: absolute;
  top: 10px;
  left: calc(var(--pct) * 100%);
  width: 4px;
  height: 44px;
  margin-left: -2px;
  --fill: var(--accent-on-dark);
  background-color: var(--fill);
  pointer-events: none;
  transition: --fill var(--hover-fade), scale 0.2s var(--ease-out);
}

.q-slider:not(.is-touched):not(.is-unsure) .q-track-line {
  animation: qLineBreathe 2.2s ease-in-out 0.6s 2;
}

@keyframes qLineBreathe {
  0%, 100% { scale: 1 1; }
  50%      { scale: 1 1.2; }
}

.q-slider.is-touched .q-track-line {
  --fill: var(--text-white);
}

/* held: a touch taller, and standing still */
.q-track:has(.q-range.is-grabbing) .q-track-line {
  scale: 1.5 1.2;
  animation: none;
}

/* The grab area: finger-sized, invisible, laid over the drawing. Vertical
   drags are left to the page, so it doesn't get in the way of scrolling. */
.q-range {
  -webkit-appearance: none;
  appearance: none;
  position: absolute;
  top: 0;
  left: calc(var(--slider-thumb) / -2);
  z-index: 1;
  width: calc(100% + var(--slider-thumb));
  height: 100%;
  margin: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: pan-y;
}

.q-range::-webkit-slider-runnable-track {
  height: 64px;
  border: none;
  background: transparent;
}

.q-range::-moz-range-track {
  height: 64px;
  border: none;
  background: transparent;
}

/* THE HANDLE: an invisible target that paints nothing. Anything drawn on it
   shows as a pale square around the line on iOS. */
.q-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--slider-thumb);
  height: 64px;
  border: none;
  border-radius: 0;
  background: transparent;
  cursor: grab;
}

.q-range::-moz-range-thumb {
  width: var(--slider-thumb);
  height: 64px;
  border: none;
  border-radius: 0;
  background: transparent;
  cursor: grab;
}

.q-range.is-grabbing::-webkit-slider-thumb {
  cursor: grabbing;
}

.q-range.is-grabbing::-moz-range-thumb {
  cursor: grabbing;
}

/* The keyboard's ring goes round the whole slider, the input being invisible */
.q-range:focus-visible {
  outline: none;
}

.q-track:has(.q-range:focus-visible) {
  outline: 2px solid var(--text-white);
  outline-offset: 4px;
}

.q-track-ends {
  display: flex;
  justify-content: space-between;
  font: var(--type-body-sm);
  color: var(--q-muted);
}

.q-slider-hint {
  margin: 0;
  font: var(--type-body-sm);
  color: var(--q-muted);
}

/* "NOT SURE YET". On a desktop it sits at the left end of the button bar,
   level with the buttons (absolute, so it arrives and leaves with its
   question); on a phone it is a full-width row under the slider. */
.q-unsure {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font: var(--type-body-sm);
  color: var(--q-muted);
  cursor: pointer;
}

.q-unsure input {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

@media (min-width: 900px) {
  .q-step > .q-unsure {
    position: absolute;
    left: var(--pane-px);
    bottom: calc(var(--pane-pb) + var(--bar-h) / 2);
    z-index: 2;
    margin: 0;
    transform: translateY(50%);
  }

  /* after an edit from the summary the two arrows take the bar's left
     end, and it moves along past them */
  .quote.is-editing .q-step > .q-unsure {
    left: calc(var(--pane-px) + 2 * var(--btn-h) + 24px);
  }
}

/* ---- Back and Next ----
   At the right, Back beside Next. After an edit from the summary
   (is-editing, script.js) the arrows, Back and a forward one, take the
   left and Next, reading Summary, keeps the right. Their ground is the
   section's own colour rather than see-through, as Our Process's
   buttons have it, so when they glide with the card's height they pass
   over the words of a question on its way out rather than through them. */
.quote-bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  margin-top: auto;
  padding-top: 28px;
}

.quote.is-editing .quote-bar {
  justify-content: space-between;
}

.quote-bar .btn {
  --fill: var(--bg-dark);
}

.quote-arrows {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* the standard buttons (.btn): Back and forward outlined squares round
   their arrows, Next its word and arrow */
.quote-back,
.quote-fwd {
  flex: none;
  overflow: hidden;
  transition: --fill var(--hover-fade), border-color var(--hover-fade),
    opacity var(--q-move) var(--ease-out), width var(--q-move) var(--ease-out),
    margin var(--q-move) var(--ease-out), visibility 0s;
}

/* On the first question there's nowhere to go back to, and on the last
   nowhere forward: the button folds away, taking its gap with it, and on
   a phone Next takes its room. */
.quote-back.is-gone,
.quote-fwd.is-gone {
  width: 0;
  margin-left: -12px;
  border-width: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--q-move) var(--ease-out), width var(--q-move) var(--ease-out),
    margin var(--q-move) var(--ease-out), visibility 0s linear var(--q-move);
}

/* With both folded away (the first question) the pair has no width left
   to take the gap beside Next with, so it takes it itself, on the same
   clock: a phone's Next stood 12px in from the card's edge there (Alban,
   27 September 2026). */
.quote-arrows {
  transition: margin var(--q-move) var(--ease-out);
}

.quote-arrows:has(> .quote-back.is-gone):has(> .quote-fwd.is-gone) {
  margin-right: -12px;
}

/* Greyed until the question is answered (aria-disabled, like every
   button that can't be used yet), so pressing it from the keyboard doesn't
   lose your place. */
.quote-next {
  overflow: hidden;
}

.quote-next-label {
  display: inline-block;
}

/* ---- The summary ----
   Review, on the last question, gives the card over to the summary
   (script.js): the questions and the plan go out of the card's flow at
   once (absolute, still at their size) and fade, the summary takes the
   flow and fades in, and the card eases from the one height to the
   other, all on the one clock, as one question gives way to the next.
   Edit plays it backwards. Nothing in the card takes a click while it
   moves (is-moving): what's under the pointer may not have faded in
   yet. */
.quote.is-reviewing .quote-grid {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  pointer-events: none;
}

/* ...and once the summary has arrived they're out of sight whatever the
   fade did: under reduced motion nothing fades, and the summary, the
   thank-you and the apology were drawn over the last question. */
.quote.is-reviewing:not(.is-moving) .quote-grid {
  visibility: hidden;
}

.quote-review {
  position: relative;
  z-index: 2;
  padding: var(--review-pt) var(--review-px) var(--review-pb);
  --review-pt: 72px;
  --review-px: 96px;
  --review-pb: 72px;
}

@media (max-width: 1199px) {
  .quote-review {
    --review-px: 56px;
  }
}

.quote-review[hidden] {
  display: none;
}

.quote-review.is-leaving {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  pointer-events: none;
}

.quote.is-moving .quote-grid,
.quote.is-moving .quote-review {
  pointer-events: none;
}

.review-inner {
  position: relative;
  width: 100%;
  max-width: 1000px;
  margin: 0 auto;
}

.review-view {
  display: flex;
  flex-direction: column;
  gap: 28px;
}

.review-view[hidden] {
  display: none;
}

/* the view on its way out (the summary, once sent) fades over the one
   arriving, out of the flow, as a question does */
.review-view.is-leaving {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}

/* the thank-you and the apology take the focus so a screen reader reads
   them; they aren't controls, so no ring */
.quote .review-view {
  outline: none;
}

.review-head {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* It takes the focus when the summary opens, so a screen reader starts
   here; it isn't a control, so no ring. */
.quote .review-title {
  margin: 0;
  font: var(--type-subhead);
  color: var(--text-white);
  text-wrap: balance;
  outline: none;
}

/* The groups: the property, each job, the new build, the timing and
   your details, two abreast where there's room, each under a small red
   heading with the outline under it */
.review-groups {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px 56px;
  align-items: start;
}

.review-group {
  display: flex;
  flex-direction: column;
  padding: 12px 0;
}

.quote .review-group-title {
  margin: 0;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--outline-on-dark);
  font: var(--type-label-xs);
  letter-spacing: var(--letter-spacing-caps);
  text-transform: uppercase;
  color: var(--accent-on-dark);
}

/* A line: its term at the left, the answer beside it and Edit at its
   end, under a hairline; the whole line is the way back to its question,
   with no hover of its own. */
.review-line {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr) auto;
  grid-template-areas: "term value edit";
  column-gap: 16px;
  row-gap: 2px;
  align-items: baseline;
  width: 100%;
  padding: 10px 0;
  border-bottom: 1px solid var(--q-line);
  text-align: left;
  color: var(--text-white);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.review-term {
  grid-area: term;
  font: var(--type-body-sm);
  color: var(--q-muted);
}

.review-value {
  grid-area: value;
  min-width: 0;
  font: var(--type-label-md);
  overflow-wrap: anywhere;
}

.review-edit {
  grid-area: edit;
  font: var(--type-label-xs);
  letter-spacing: var(--letter-spacing-caps);
  text-transform: uppercase;
  color: var(--q-dim);
}

/* The foot: the response promise at the left and Send at the right, the
   one filled button in the form (CLAUDE.md, "Design rules"). Send's own
   message, should it turn anything back, sits over the row. */
.review-foot {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 8px;
  padding-top: 16px;
}

.review-foot-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px 32px;
}

.review-send {
  flex: none;
}

/* while it sends, held at its pressed red and the pointer waiting */
.review-send.is-sending,
.review-send.is-sending:hover {
  border-color: var(--accent-dark);
  --fill: var(--accent-dark);
  cursor: wait;
  /* not greyed as a disabled button is (.btn:disabled): it is working */
  opacity: 1;
}

/* The response promise beside Send, its bolt in red and kept to the
   first line should the words wrap */
.planner-promise {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 0;
  font: var(--type-label-sm);
  color: var(--q-soft);
}

.planner-promise .icon {
  flex: none;
  font-size: 0.8125rem;
  color: var(--accent-on-dark);
}

/* Send's own message, should it turn anything back */
.planner-error {
  display: none;
  margin: 0;
  font: var(--type-body-sm);
  color: var(--accent-on-dark);
  text-align: center;
}

.planner-error.show {
  display: block;
}

/* SENT, OR COULDN'T SEND: in the same layer, which stays open */
.review-done {
  gap: 24px;
  max-width: 620px;
}

.review-fail {
  max-width: 640px;
}

.review-badge {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  border: 1px solid var(--accent-on-dark);
  border-radius: var(--radius);
  background: rgba(var(--accent-rgb), 0.15);
  color: var(--text-white);
  font-size: 24px;
}

.review-badge .icon {
  vertical-align: 0;
}

.review-badge-warn {
  border-color: var(--outline-on-dark);
  background: transparent;
  color: var(--accent-on-dark);
  font-size: 26px;
}

.quote .review-done-title {
  margin: 0;
  font: var(--type-subhead);
  color: var(--text-white);
  text-wrap: balance;
}

/* What happens next, under the thank-you */
.review-next {
  margin: 0;
  font: var(--type-body-lg);
  color: var(--q-soft);
}

.review-fail-title {
  margin: 0;
  font: var(--type-heading-2xl);
  color: var(--text-white);
}

.review-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* ---- Phones and small tablets ---- */
@media (max-width: 899px) {
  .quote-grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "pane";
  }

  .quote-rail {
    display: none;
  }

  .quote-pane {
    min-height: 560px;
    --pane-pt: 28px;
    --pane-px: 24px;
    --pane-pb: 32px;
  }

  /* One hairline along the card's top edge, inside its outline, red as
     far as the share of the questions answered (--share, script.js); it
     grows on the form's one clock. */
  .quote-progress {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
    background: rgba(255, 255, 255, 0.12);
    --share: 0;
  }

  .quote-progress-fill {
    display: block;
    height: 100%;
    background: var(--accent-on-dark);
    transform: scaleX(var(--share));
    transform-origin: 0 50%;
    transition: transform var(--q-move) var(--ease-out);
  }

  .quote-meta {
    margin-bottom: 0;
  }

  .quote-count {
    gap: 8px;
  }

  /* "Answers [n]": opens the drawer below */
  .answers-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 40px;
    padding: 0 14px;
    border: 1px solid var(--outline-on-dark);
    border-radius: var(--radius);
    background: transparent;
    color: var(--text-white);
    font: var(--type-label-sm);
    cursor: pointer;
    transition: border-color var(--hover-fade);
  }

  .answers-toggle[hidden] {
    display: none;
  }

  .answers-n {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    border: 1px solid rgba(var(--accent-on-dark-rgb), 0.6);
    border-radius: var(--radius);
    font: var(--type-label-xs);
  }

  .answers-toggle .icon {
    font-size: 11px;
    opacity: 0.7;
    transition: rotate var(--q-move) var(--ease-out);
  }

  .answers-toggle[aria-expanded="true"] .icon {
    rotate: 180deg;
  }

  .answers-drawer {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows var(--q-move) var(--ease-out);
  }

  .answers-drawer.is-open {
    grid-template-rows: 1fr;
  }

  .answers-clip {
    min-height: 0;
    overflow: hidden;
    visibility: hidden;
    transition: visibility 0s linear var(--q-move);
  }

  .answers-drawer.is-open .answers-clip {
    visibility: visible;
    transition: visibility 0s;
  }

  .answers-list {
    margin: 16px 0 0;
    padding: 6px 0 4px;
    list-style: none;
    border-top: 1px solid var(--q-line);
    border-bottom: 1px solid var(--q-line);
  }

  /* An answer: the plan's block (the step's name and its answers), with
     its number and Edit either side; they fade in as the drawer opens.
     The three stand from the row's top, the number and Edit centred on
     the name's line as the plan's number is, rather than on its
     baseline: to find a baseline Safari lays the block out at its
     narrowest first, a letter to a line, and the answer line's own grid
     kept that height, so an answer stood over some 380px of empty drawer
     (on an iPhone, 27 September 2026). */
  .answer-row {
    display: grid;
    grid-template-columns: 28px minmax(0, 1fr) auto;
    column-gap: 12px;
    align-items: start;
    width: 100%;
    padding: 12px 0;
    text-align: left;
    color: var(--text-white);
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--q-move) var(--ease-out);
  }

  .answers-drawer.is-open .answer-row {
    opacity: 1;
  }

  .answer-row:focus-visible {
    outline-offset: -2px;
  }

  /* a line box of the name's height (12px at 1.7 is 20.4px, the name's
     20px), as the plan's number has */
  .answer-num,
  .answer-edit {
    font: var(--type-label-xs);
    line-height: var(--line-height-relaxed);
    letter-spacing: var(--letter-spacing-caps);
  }

  .answer-num {
    color: var(--q-dim);
  }

  .answer-edit {
    text-transform: uppercase;
    color: var(--accent-on-dark);
  }

  .quote-stage {
    margin-top: 28px;
  }

  /* the reading is smaller here, and its unit steps down with it */
  .q-readout-unit {
    font: var(--type-numeral-2xl);
  }

  .q-step > .q-unsure {
    display: flex;
    gap: 12px;
    min-height: var(--btn-h);
    margin-top: 18px;
    padding: 0 16px;
    border: 1px solid var(--outline-on-dark);
    border-radius: var(--radius);
    background: transparent;
    font: var(--type-body-md);
    color: var(--q-soft);
  }

  .q-unsure input {
    width: 20px;
    height: 20px;
  }

  .q-tile {
    padding: 24px 12px;
  }

  /* Back and Next, and Send under the summary, scroll with the card, at the
     foot of it, as on a larger screen */
  .quote-next {
    flex: 1;
  }

  .quote-review {
    --review-pt: 40px;
    --review-px: 24px;
    --review-pb: 32px;
  }

  /* the groups one under another, each line its term over its answer,
     and Send across the width with the promise centred under it */
  .review-groups {
    grid-template-columns: minmax(0, 1fr);
  }

  .review-line {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "term edit" "value value";
  }

  .review-foot-row {
    flex-direction: column-reverse;
    align-items: stretch;
  }

  .review-send {
    width: 100%;
  }

  .planner-promise {
    justify-content: center;
  }
}

/* No movement under reduced motion: every change lands at once (script.js
   skips its own animations too). */
@media (prefers-reduced-motion: reduce) {
  .quote *,
  .quote *::before,
  .quote *::after {
    transition-duration: 0s !important;
    transition-delay: 0s !important;
    animation: none !important;
  }
}

/* The service cards' own icons: filled silhouettes of their own design,
   the one kind of icon that isn't drawn in Tabler's lines (CLAUDE.md,
   "Design rules"). The part being worked on is drawn full strength and
   the existing building faintly, so each reads at a glance. */
.icon-svg {
  fill: currentColor;
  stroke: none;
  display: block;
}

.service-icon .icon-svg {
  width: 1.95rem;
  height: 1.95rem;
}

/* ==========================================
   "WHERE TO MEASURE" HELP
   The ? beside the head-height question, and the panel it opens.
   ========================================== */
.help-dot {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--outline-on-dark);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-white);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transition: border-color var(--hover-fade);
}

/* Grows the tappable area past the visible square to about 50 x 46. Kept
   shallower below than above, clear of the reading underneath. */
.help-dot::after {
  content: "";
  position: absolute;
  top: -10px;
  right: -10px;
  bottom: -6px;
  left: -10px;
}

@media (hover: hover) {
  .help-dot:hover {
    border-color: var(--accent-on-dark);
  }
}

/* A <dialog>: the browser puts it above everything, keeps the focus in it
   and sets the page aside. margin: auto (taken away by the reset) centres
   it. */
.help-card {
  width: calc(100% - 3rem);
  max-width: 380px;
  max-height: calc(100% - 3rem);
  margin: auto;
  padding: 2rem 1.75rem 1.6rem;
  border-radius: var(--radius);
  background: var(--bg-white);
  border: 1px solid var(--outline-on-light);
  color: var(--text-dark);
  text-align: center;
  /* scrolls in a window too short for it */
  overflow: auto;
  /* script.js flies it from the ? and back, and HELP_FLIGHT_MS waits for
     this duration: change the two together. */
  transition: transform 0.55s cubic-bezier(0.16, 1, 0.3, 1),
              opacity 0.34s ease;
}

/* the dimming comes up and goes with the card rather than snapping on */
.help-card::backdrop {
  background: rgba(8, 10, 13, 0.72);
  opacity: 0;
  transition: opacity 0.4s ease;
}

.help-card.is-open::backdrop {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .help-card,
  .help-card::backdrop {
    transition: none;
  }
}

.help-card h4 {
  font: var(--type-heading-xl);
  margin-bottom: 1rem;
}

/* Dark ink on white paper, as a technical drawing reads, which gives the
   red dimension arrow its contrast. --help-paper fills the SVG's <rect>;
   `color` feeds its currentColor strokes. */
.help-diagram {
  display: block;
  width: 100%;
  height: auto;
  margin-bottom: 1rem;
  --help-paper: #ffffff;
  color: #20242b;
}

.help-card p {
  font: var(--type-body-sm);
  color: #4a5059;
}

.help-card strong {
  color: var(--text-dark);
  font-weight: var(--font-weight-semibold);
}

.help-close {
  position: absolute;
  top: 0.6rem;
  right: 0.7rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: 1rem;
  cursor: pointer;
  transition: var(--transition-fast);
}

@media (hover: hover) {
  .help-close:hover {
    color: var(--text-dark);
  }
}

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

/* ==========================================
   THE PROJECTS
   A strip of framed cards, one to a project, that scrolls sideways: the
   photo at the top of the card and its name and place under it, inside
   one outline. Two cards fill the page's width on a desktop, with
   the third running off under the screen's edge; a phone shows one and
   the edge of the next. The strip runs to the screen's edges rather than
   the container's, the one section that does (CLAUDE.md): the page's
   margin is inside it as padding, so at rest the first card lines up
   with the title, and scrolling, the cards pass under the edge of the
   screen instead of stopping at the margin. Each card snaps into line
   with the margin, and the gap between cards is the margin's width, so
   the card before ends exactly at the screen's edge, with no sliver of
   it left. The arrows beside the title move it a card at a time
   (script.js) and grey out at either end; on a phone they go, and a
   finger swipes it. No scrollbar: the card running off the edge says
   there's more.
   ========================================== */
.portfolio-strip {
  /* the screen's edge to the container's content, each side: the sum
     .container makes of its centring and its padding */
  --strip-edge-left: calc(max(0px, (100% - var(--container-max)) / 2) + max(var(--gutter), env(safe-area-inset-left)));
  --strip-edge-right: calc(max(0px, (100% - var(--container-max)) / 2) + max(var(--gutter), env(safe-area-inset-right)));
  /* the cards' offsets are measured from here (script.js) */
  position: relative;
  display: flex;
  gap: var(--gutter);
  /* 5px above and below the cards for a focus ring, which the strip's
     clipping would otherwise cut off; the title's gap and the Instagram
     bar's margin give the 5px back */
  margin-top: -5px;
  padding: 5px var(--strip-edge-right) 5px var(--strip-edge-left);
  overflow-x: auto;
  scrollbar-width: none;
  /* a swipe past the strip's start isn't the browser's way back */
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--strip-edge-left) var(--strip-edge-right);
}

/* It glides from card to card, unless the device asks for less motion
   (script.js asks for 'auto', which follows this). */
@media (prefers-reduced-motion: no-preference) {
  .portfolio-strip {
    scroll-behavior: smooth;
  }
}

.portfolio-strip::-webkit-scrollbar {
  display: none;
}

/* Back and next: plain icons at either side of the heading, level with
   the title and its line; greyed at either end */
.portfolio-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  color: var(--text-dark);
  font-size: 1.5rem;
  cursor: pointer;
  transition: color var(--hover-fade), opacity 0.25s var(--ease-out);
}

.portfolio-arrow[aria-disabled="true"] {
  opacity: 0.25;
  cursor: not-allowed;
}

@media (hover: hover) {
  .portfolio-arrow:not([aria-disabled="true"]):hover {
    color: var(--accent);
  }
}

/* on a phone a finger swipes the strip, which is the natural way, so the
   arrows go */
@media (max-width: 768px) {
  .portfolio-arrow {
    display: none;
  }
}

/* A card: the photo, then the caption, in one outline that turns red under
   the mouse. Two to a desktop screen (540px), one to a phone's (290px), and
   never wider than the strip's room between the margins (a 320px phone);
   a phone on its side sizes it from the height instead (below). Its
   leading edge is what snaps into line with the margin. It keeps that
   width whatever its title's length (min-width: 0): a title held to one
   line would otherwise widen its card to fit. */
.portfolio-item {
  flex: 0 0 min(540px, 100%);
  min-width: 0;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--outline-on-light);
  border-radius: var(--radius);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color var(--hover-fade);
}

/* the photo fills the top of the card, cropped to a portrait: 4:5 on a
   desktop, 3:4 on a phone */
.portfolio-photo {
  aspect-ratio: 4 / 5;
  overflow: hidden;
}

.portfolio-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Hovering turns the outline red and zooms the photo. The zoom is
   movement, so it goes when less motion is asked for. */
@media (hover: hover) {
  .portfolio-item:hover {
    border-color: var(--accent);
  }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .portfolio-item img {
    transition: transform 0.8s var(--ease-out);
  }

  .portfolio-item:hover img {
    transform: scale(1.08);
  }
}

/* the caption: the project's name over its place */
.portfolio-caption {
  padding: 1.5rem 1.75rem 1.75rem;
}

/* The name stands on one line, so every caption is one height: where
   Borehamwood's took two, up to a tablet's width, every other card stood
   as tall, with an empty band under its caption (Alban, 27 September
   2026). A name too long for the card fades out at its end, as the
   plan's edges fade where there's more (script.js marks it, .is-cut);
   without the script it ends in an ellipsis. */
.portfolio-item-title {
  font: var(--type-heading-xl);
  color: var(--text-dark);
  margin-bottom: 0.25rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.portfolio-item-title.is-cut {
  text-overflow: clip;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 3rem), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 3rem), transparent);
}

.portfolio-loc {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* a phone's card: narrower, with a taller crop */
@media (max-width: 768px) {
  .portfolio-item {
    flex-basis: min(290px, 100%);
  }

  .portfolio-photo {
    aspect-ratio: 3 / 4;
  }
}

/* the caption a step smaller on a phone, upright or on its side */
@media (max-width: 768px), (max-height: 500px) {
  .portfolio-caption {
    padding: 1rem 1.125rem 1.125rem;
  }

  .portfolio-item-title {
    font: var(--type-heading-md);
    margin-bottom: 0.125rem;
  }

  .portfolio-loc {
    font: var(--type-body-xs);
  }
}

/* A phone on its side is wider than the phone rules reach but far shorter
   than a desktop's card, which stood twice the screen's height (a project's
   video, watched sideways, closes back onto the strip). So a short screen
   sizes the card from the height instead, with a landscape crop: the photo
   takes the height left under the header and the caption (svh: the
   screen's height with the browser's bars showing), and the card the
   width that makes it 4:3. */
@media (max-height: 500px) {
  .portfolio-item {
    flex-basis: min(540px, 100%, calc((100svh - var(--header-h) - 8.75rem) * 4 / 3));
  }

  .portfolio-photo {
    aspect-ratio: 4 / 3;
  }
}

/* Instagram prompt, centred under the projects: the whole card is the
   link, with the link-out icon at the right in the text's colour */
.insta-prompt {
  position: relative;
  overflow: hidden;
  /* large enough to be read, well short of competing with the projects */
  width: 100%;
  max-width: 680px;
  margin-left: auto;
  margin-right: auto;
  /* 48px under the strip's cards in all, with the strip's own 5px */
  margin-top: calc(3rem - 5px);
  padding: 1.5rem 1.75rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  flex-wrap: wrap;
  background-color: transparent;
  border: 1px solid var(--outline-on-light);
  border-radius: var(--radius);
  color: var(--text-dark);
  transition: border-color var(--hover-fade);
}

.insta-prompt-go {
  flex: none;
  font-size: 1.25rem;
  transition: color var(--hover-fade);
}

@media (hover: hover) {
  .insta-prompt:hover {
    border-color: var(--accent);
  }

  .insta-prompt:hover .insta-prompt-icon {
    --fill: var(--accent);
    color: var(--text-white);
  }

  .insta-prompt:hover .insta-prompt-go {
    color: var(--accent);
  }
}

.insta-prompt-id {
  display: flex;
  align-items: center;
  gap: 1.1rem;
}

/* the tile matches the height of the two lines of text beside it */
.insta-prompt-icon {
  width: 64px;
  height: 64px;
  border-radius: var(--radius);
  --fill: var(--accent-light);
  background-color: var(--fill);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.8rem;
  flex-shrink: 0;
  transition: color var(--hover-fade), --fill var(--hover-fade);
}

.insta-prompt-text {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.insta-prompt-handle {
  font: var(--type-heading-xl);
  color: var(--text-dark);
}

.insta-prompt-line {
  font: var(--type-body-md);
  color: var(--text-muted);
}

@media (max-width: 768px) {
  .insta-prompt {
    width: 100%;
    /* 32px under the cards in all, with the strip's 5px */
    margin-top: calc(2rem - 5px);
  }
}

/* Small phones: still one row, @albhomes on the left and the link-out
   icon on the right, trimmed to fit, with the text allowed to shrink so it
   never overflows. */
@media (max-width: 560px) {
  .insta-prompt {
    flex-wrap: nowrap;
    gap: 0.75rem;
    /* compact padding: on a phone the card runs full width anyway */
    padding: 1rem 1.15rem;
  }

  /* flex items won't shrink below their content unless told they may */
  .insta-prompt-id,
  .insta-prompt-text {
    min-width: 0;
  }

  /* …and the compact tile and handle with it, or the taller tile pushes the
     strapline on to a second line and the strip grows. */
  .insta-prompt-id {
    gap: 0.7rem;
  }

  .insta-prompt-icon {
    width: 48px;
    height: 48px;
    border-radius: var(--radius);
    font-size: 1.45rem;
  }

  .insta-prompt-handle {
    font: var(--type-heading-md);
  }

  .insta-prompt-line {
    font: var(--type-body-xs);
  }
}

/* 320px phones: a smaller tile, or the strapline runs to a third line */
@media (max-width: 360px) {
  .insta-prompt-icon {
    width: 38px;
    height: 38px;
    border-radius: var(--radius);
    font-size: 1.15rem;
  }
}

/* ==========================================
   LEGAL AND 404 PAGES (privacy.html, 404.html)
   ========================================== */
.legal-page {
  max-width: 760px;
  padding-top: calc(4rem + env(safe-area-inset-top));
  padding-bottom: 5rem;
}

.legal-back {
  color: var(--accent);
  font: var(--type-label-md);
  text-decoration: none;
}

@media (hover: hover) {
  .legal-back:hover {
    color: var(--accent-dark);
  }
}

/* the page's title, as a section's title on the home page */
.legal-page h1 {
  font: var(--type-title);
  letter-spacing: var(--letter-spacing-tight);
  color: var(--text-dark);
  margin: 1.5rem 0 0.4rem 0;
}

/* .legal-page named, to outrank the paragraph rule below */
.legal-page .legal-updated {
  color: var(--text-muted);
  font: var(--type-body-sm);
  margin-bottom: 2.25rem;
}

.legal-page h2 {
  font: var(--type-heading-xl);
  color: var(--text-dark);
  margin: 2rem 0 0.75rem 0;
}

.legal-page p,
.legal-page li {
  font: var(--type-body-md);
  color: var(--text);
  margin-bottom: 0.9rem;
}

.legal-page ul {
  padding-left: 1.25rem;
}

/* a link in the text is underlined, so it can be told from the words
   round it (the reset takes every link's underline away); a button in a
   paragraph, as the 404 page's is, is no link in the text */
.legal-page :is(p, li) a:not(.btn) {
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
  transition: color var(--hover-fade);
}

@media (hover: hover) {
  .legal-page :is(p, li) a:not(.btn):hover {
    color: var(--accent);
  }
}

/* ==========================================
   OUR PROCESS
   The six stages along a measured scale, a column each: a column's edge is
   a major tick, and under the names a bar runs the scale's length, red up to
   the stage on show. Below it that stage, in a card that travels under
   the scale to sit below its column, with Back and Next; the last stage
   carries the guarantee. Square throughout, like a drawing: its lines, the
   card and the icon's tile. From
   1101px each column carries its stage's name, and below that its number
   only. script.js says which stage is on show (--step).
   ========================================== */
.process {
  /* the section's greys, and its lines. The scale is one object in one
     colour, solid rather than see-through, so where its parts meet (the
     bar's track, the columns' edges and the ticks) nothing shows lighter;
     --p-edge outlines the card and the icon's tile. */
  --p-soft: #d2d5da;
  --p-muted: #9aa0a8;
  --p-rule: #3c3d40;
  --p-edge: var(--outline-on-dark);
  /* the scale: how many stages it has, the ruler under the bar, how many
     parts its minor ticks cut a stage into (ten, and five below 1101px), and
     the room between a column's words and the bar */
  --stages: 6;
  --ruler-h: 16px;
  --divisions: 10;
  --bar-gap: 18px;
  /* the stage on show, counted from 0 (script.js sets it): how far the bar
     is red, and which column the card sits under */
  --step: 0;
  /* the card: as wide as reads well, never wider than the scale (the room
     inside it is every card's, --card-pad) */
  --card-w: min(100cqw, 38rem);
  /* One clock for everything a change of stage moves, on the site's
     --ease-out: the bar, the colours, the card's travel and height, the
     crossfade of its words, Next's width and the buttons' move (script.js
     reads it too). It's the site's --move, which the quote form keeps
     too. */
  --p-move: var(--move);
}

/* ---- The scale ---- */
.process-scale {
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--stages), minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
  border-right: 1px solid var(--p-rule);
}

/* The bar: a track the scale's length, in the scale's colour, and the red
   over it, to the end of the stage on show. One piece for the whole scale,
   so a jump of several stages is one movement. Over the columns, so the red
   runs unbroken across their edges, and the track meets them as one line. */
.process-scale::before,
.process-scale::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--ruler-h);
  z-index: 2;
  height: 6px;
  background: var(--p-rule);
  pointer-events: none;
}

.process-scale::after {
  background: var(--accent-on-dark);
  transform: scaleX(calc((var(--step) + 1) / var(--stages)));
  transform-origin: 0 50%;
  transition: transform var(--p-move) var(--ease-out);
}

.process-scale > li {
  position: relative;
  z-index: 1;
  display: flex;
}

/* A stage's column, all of it a button. Every column is as tall as the
   tallest, so the bar runs level under them whatever the names do. */
.process-stop {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  padding-bottom: calc(var(--bar-gap) + 6px + var(--ruler-h));
  border-left: 1px solid var(--p-rule);
  color: var(--p-muted);
  text-align: left;
  cursor: pointer;
  /* on the stage's clock, and on the even curve every colour takes */
  transition: color var(--p-move) ease-in-out;
}

/* The minor ticks, along the top of the ruler between the column's edges
   (its major ticks), never on them: a tick at the end of each part but the
   last, across all but the last part of the column. */
.process-stop::before {
  content: "";
  position: absolute;
  left: 0;
  bottom: calc(var(--ruler-h) - 8px);
  width: calc(100% - 100% / var(--divisions));
  height: 8px;
  background: repeating-linear-gradient(90deg, transparent 0 calc(100% / (var(--divisions) - 1) - 1px),
    var(--p-rule) 0 calc(100% / (var(--divisions) - 1)));
}

/* inside the column: outside, it would run over the next one's tick */
.process-stop:focus-visible {
  outline-offset: -2px;
}

.process-stop[aria-current="step"] {
  color: var(--text-white);
  cursor: default;
}

@media (hover: hover) {
  .process-stop:hover {
    color: var(--text-white);
  }
}

.process-stop-num {
  padding: 0 16px 8px;
  font: var(--type-label-xs);
  letter-spacing: var(--letter-spacing-caps);
}

.process-stop-name {
  padding: 0 20px 0 16px;
  font: var(--type-heading-xl);
  text-wrap: balance;
}

/* ---- The stage on show ----
   A card under the scale that travels to sit below the column of
   the stage on show: centred on it, or against the scale's end where there
   isn't room either side. Its travel is measured in the scale's width (cqw),
   so the detail is a container. */
.process-detail {
  container-type: inline-size;
  margin-top: 40px;
}

/* As tall as the stage on show; clipped, so while its height eases to the
   next nothing shows past its edge */
.process-card {
  width: var(--card-w);
  padding: var(--card-pad);
  overflow: clip;
  border: 1px solid var(--p-edge);
  border-radius: var(--radius);
  translate: clamp(0px, calc((var(--step) + 0.5) * 100cqw / var(--stages) - var(--card-w) / 2),
    calc(100cqw - var(--card-w))) 0;
  transition: translate var(--p-move) var(--ease-out);
}

/* The stage on show is the one in the card's flow, so the card takes its
   height (script.js eases it there). The one it replaces fades out over it,
   out of the flow; the rest take no room. */
.process-stages {
  position: relative;
  /* its first stage's margin (below) stays its own rather than running
     out through it, so a stage in the flow and one leaving it line up */
  display: flow-root;
}

/* A stage fades as a whole with its icon's outlined tile at its corner, so
   it keeps 2px round what it holds, taken back by its margin: a WebKit
   layer snapped to whole pixels cut into the outline otherwise
   (CLAUDE.md). */
.process-stage {
  --stage-gap: 24px;
  padding: 2px;
  margin: -2px;
}

.process-stage:not(.is-current, .is-leaving) {
  display: none;
}

.process-stage.is-leaving {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}

.process-stage > * + * {
  margin-top: var(--stage-gap);
}

/* The stage's icon in an outlined tile, and beside it, in red, what it costs or how
   long it takes, where that's the point */
.process-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
}

.process-icon {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--p-edge);
  border-radius: var(--radius);
  color: var(--text-white);
  font-size: 16px;
}

.process-tag {
  font: var(--type-label-xs);
  letter-spacing: var(--letter-spacing-caps);
  text-transform: uppercase;
  color: var(--accent-on-dark);
}

.process-title {
  font: var(--type-subhead);
}

.process-text {
  font: var(--type-body-lg);
  color: var(--p-soft);
  text-wrap: pretty;
}

/* A point under a stage's words: a promise made at that stage, under a
   hairline, with a red icon. Where a stage has several they stack, each
   under its own hairline; the guarantee is the last stage's last. */
.process-point {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-top: 24px;
  border-top: 1px solid var(--p-rule);
}

.process-point-title {
  display: flex;
  align-items: center;
  gap: 12px;
  font: var(--type-heading-xl);
  color: var(--text-white);
}

.process-point-title .icon {
  flex: none;
  font-size: 20px;
  color: var(--accent-on-dark);
}

.process-point-text {
  font: var(--type-body-md);
  color: var(--p-muted);
}

/* Back and Next, at the foot of the card */
.process-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  /* It glides (translate, script.js) with Back and Next's outlines on its
     edges, so it keeps 2px round them, taken back by its margins (32px
     above the buttons): a WebKit layer snapped to whole pixels cut into
     them otherwise (CLAUDE.md). */
  padding: 2px;
  margin: 30px -2px -2px;
}

/* The standard buttons (.btn), as the quote form's: Back an arrow alone
   (.btn-icon), and Next its word, the next stage's name and its arrow,
   greyed on the last stage. Their ground is the section's own colour
   rather than see-through, so when they move with the card's height they
   pass over the words rather than through them; and they change on the
   stage's clock. */
.btn.process-btn {
  overflow: hidden;
  background-color: var(--bg-dark);
  transition: border-color var(--p-move) ease-in-out, opacity var(--p-move) var(--ease-out);
}

/* On the first stage there's nowhere to go back to: Back folds away,
   taking its gap with it, and Next moves up (on a phone, takes its room),
   as the quote form's Back does on its first question (Alban, 27
   September 2026: the two stood half and half, Back with its word). */
.btn.process-back {
  flex: none;
  transition: border-color var(--p-move) ease-in-out, opacity var(--p-move) var(--ease-out),
    width var(--p-move) var(--ease-out), margin var(--p-move) var(--ease-out), visibility 0s;
}

.btn.process-back.is-gone {
  width: 0;
  margin-right: -12px;
  border-width: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--p-move) var(--ease-out), width var(--p-move) var(--ease-out),
    margin var(--p-move) var(--ease-out), visibility 0s linear var(--p-move);
}

/* the stage after this one, named as it's written */
.process-next-name {
  letter-spacing: normal;
  text-transform: none;
}

/* ---- Tablets: numbers on the scale, the names in the card below ---- */
@media (max-width: 1100px) {
  .process {
    --divisions: 5;
    --bar-gap: 4px;
  }

  .process-stop-num {
    padding: 0 12px 10px;
  }

  /* read out, not shown */
  .process-stop-name {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .process-stage {
    --stage-gap: 20px;
  }

  .process-nav {
    margin-top: 26px;
  }
}

/* ---- Phones: the card as wide as the scale, and Next taking the rest of
   the row beside Back ---- */
@media (max-width: 767px) {
  .process-stop-num {
    padding: 0 0 10px 8px;
  }

  .process-detail {
    margin-top: 32px;
  }

  .process-stage {
    --stage-gap: 18px;
  }

  .process-nav {
    margin-top: 22px;
  }

  .process-next {
    flex: 1;
  }

  .process-next-name {
    display: none;
  }
}

/* No movement under reduced motion: the bar, the colours and the card
   change at once (script.js skips its own animations too) */
@media (prefers-reduced-motion: reduce) {
  .process *,
  .process *::before,
  .process *::after {
    transition-duration: 0s !important;
    transition-delay: 0s !important;
  }
}

/* ==========================================
   CONTACT
   ========================================== */
.contact-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4rem;
  align-items: start;
}

@media (max-width: 1024px) {
  .contact-layout {
    grid-template-columns: 1fr;
    gap: 3rem;
  }
}

/* Info Side */
.contact-info-panel {
  text-align: center;
}

/* The phone (WhatsApp or a call: one number) and email, side by side, and
   in that order in the footer too. One above the other up to 640px, where
   side by side the address would fit only in a size too small to read at a
   glance, and three buttons would barely fit across. */
.contact-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--card-gap);
}

@media (max-width: 640px) {
  .contact-cards { grid-template-columns: minmax(0, 1fr); }
}

/* Drawn like Our Process's card: an outline, with what it is in small
   capitals, the number or the address large under it, and along its foot
   the buttons that use it. The card itself is no button, so the number
   and the address can be picked out like any text. Its width is the one
   its text is sized to (below). */
.contact-card {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  gap: 0.35rem;
  padding: var(--card-pad);
  background-color: transparent;
  border: 1px solid var(--outline-on-light);
  border-radius: var(--radius);
}

.contact-card h3 {
  font: var(--type-label-xs);
  letter-spacing: var(--letter-spacing-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The number and the address in a card's heading size, a step smaller
   where the card is too narrow for the address at it (side by side just
   above a phone's width, and a small phone's single file), and a step
   smaller again where that won't fit either, so it always fits on its
   line; the two cards are always one width, so they step together. Only
   a screen narrower than any phone breaks it onto a second line. */
.contact-card-detail {
  margin-bottom: 1.25rem;
  font: var(--type-heading-2xl);
  color: var(--text-dark);
  overflow-wrap: anywhere;
}

@container (max-width: 15rem) {
  .contact-card-detail {
    font: var(--type-heading-xl);
  }
}

@container (max-width: 12.5rem) {
  .contact-card-detail {
    font: var(--type-heading-md);
  }
}

/* The buttons: the standard square ones, an icon in each (.btn-icon), in
   a row at the foot of the card, level in the two cards */
.contact-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: auto;
}

/* Copy: its icon, and the tick that takes its place for a moment once the
   number or the address is copied (script.js), one over the other. The
   one going shrinks as it fades; a beat later the one coming bounces in,
   swelling just past its size and settling back. The tick is red. Reduced
   motion only fades them. */
.contact-copy {
  display: inline-grid;
  place-items: center;
}

.contact-copy .icon {
  grid-area: 1 / 1;
  transition: opacity 0.15s ease-in, transform 0.15s ease-in;
}

.contact-copy .icon-check {
  opacity: 0;
  color: var(--accent);
}

.contact-copy.is-copied .icon-copy {
  opacity: 0;
}

.contact-copy:not(.is-copied) .icon-copy,
.contact-copy.is-copied .icon-check {
  opacity: 1;
  transform: none;
  transition: opacity 0.2s ease-out 0.08s, transform 0.45s cubic-bezier(0.3, 2, 0.5, 1) 0.08s;
}

@media (prefers-reduced-motion: no-preference) {
  .contact-copy .icon-check,
  .contact-copy.is-copied .icon-copy {
    transform: scale(0.4);
  }
}

/* the second title in the contact section, well clear of the cards above */
/* The FAQs' heading: a subhead, a step under the section's title (Contact
   Us), and centred like it, with the title's gap under it */
.faqs-heading {
  margin: 5rem 0 var(--title-gap);
  font: var(--type-subhead);
  text-align: center;
}

/* ==========================================
   FAQS ACCORDION
   ========================================== */
.faqs-layout {
  text-align: left;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--card-gap);
  align-items: start;
}

@media (max-width: 700px) {
  .faqs-layout {
    grid-template-columns: 1fr;
  }
}

/* The whole card opens and shuts its answer (script.js), so all of it
   answers the pointer; the question and its + take the colour. */
.faq-item {
  position: relative;
  background-color: transparent;
  border-radius: var(--radius);
  border: 1px solid var(--outline-on-light);
  overflow: hidden;
  cursor: pointer;
  transition: var(--transition-fast);
}



.faq-trigger {
  width: 100%;
  padding: var(--card-pad);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1.25rem;
  cursor: pointer;
  text-align: left;
  font: var(--type-label-lg);
  color: var(--text-dark);
  transition: var(--transition-fast);
  user-select: none;
}

/* the keyboard's ring inside the card, whose own edge (overflow: hidden)
   clipped the ring drawn outside it */
.faq-trigger:focus-visible {
  outline-offset: -4px;
}

/* a size down on a phone, where the longer questions ran to three lines */
@media (max-width: 480px) {
  .faq-trigger {
    font: var(--type-label-md);
  }
}

@media (hover: hover) {
  .faq-item:hover .faq-trigger {
    color: var(--accent);
  }
}

.faq-item.active .faq-trigger {
  color: var(--accent);
}

.faq-icon {
  font-size: 0.95rem;
  color: var(--text-muted);
  flex: none;
  transition: var(--transition);
}

@media (hover: hover) {
  .faq-item:hover .faq-icon {
    color: var(--accent);
  }
}

.faq-item.active .faq-icon {
  transform: rotate(45deg);
  color: var(--accent);
}

/* Opens by growing a grid row from 0fr to 1fr, which animates to the
   answer's real height at any width. The padding sits on the paragraph,
   inside the clipping box, so a shut answer takes no room; visibility
   follows, so it isn't read out either. */
.faq-content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

.faq-item.active .faq-content {
  grid-template-rows: 1fr;
}

.faq-content-inner {
  min-height: 0;
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s linear 0.4s;
  color: var(--text-muted);
  font: var(--type-body-md);
}

.faq-item.active .faq-content-inner {
  visibility: visible;
  transition: visibility 0s;
}

/* the card's room at its sides and foot; the question's own foot is the
   room above */
.faq-content-inner p {
  padding: 0 var(--card-pad) var(--card-pad);
}

@media (prefers-reduced-motion: reduce) {
  .faq-content,
  .faq-content-inner {
    transition: none;
  }
}

/* ==========================================
   FOOTER
   A line on the firm, the page's links, the four ways to reach us with the
   Google rating under them, and the legal line: centred rows on a phone,
   one band on a desktop.
   ========================================== */
.footer {
  background-color: var(--bg-dark);
  color: #9aa0a8;
  padding: 3.5rem 0 2.5rem;
  text-align: center;
}

.footer .container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.6rem;
}

.footer-line {
  max-width: 34rem;
  margin: 0;
  font: var(--type-body-md);
  color: #d2d5da;
}

/* The links in one row that wraps on a narrow screen; the gap does the
   separating, so there are no bullets or bars to tidy. */
.footer-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem 1.6rem;
}

.footer-link {
  font: var(--type-label-sm);
  color: #d2d5da;
  transition: var(--transition-fast);
}

@media (hover: hover) {
  .footer-link:hover {
    color: var(--text-white);
  }
}

/* WhatsApp, phone and email, as in Contact Us, then Instagram: the
   standard buttons, an icon in each (.btn-icon). The rating under them
   takes the row's width. */
.footer-reach {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.footer-reach-row {
  display: flex;
  gap: 0.75rem;
}

/* The Google rating, drawn like the buttons over it: square, outlined, and
   as wide as their row. The average, then the stars over the number of
   reviews, set apart by a hairline, and the arrow out at the right; the
   whole of it the way to the reviews on Google. */
.footer-rating {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 0.75rem;
  row-gap: 0.2rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--outline-on-dark);
  border-radius: var(--radius);
  background: transparent;
  text-align: left;
}

@media (hover: hover) {
  .footer-rating:hover {
    border-color: var(--accent-on-dark);
  }

  .footer-rating:hover .footer-rating-total,
  .footer-rating:hover .footer-rating-go {
    color: var(--text-white);
  }
}

.footer-rating-score {
  grid-row: span 2;
  align-self: stretch;
  display: flex;
  align-items: center;
  padding-right: 0.75rem;
  border-right: 1px solid rgba(255, 255, 255, 0.12);
  font: var(--type-numeral-2xl);
  color: var(--text-white);
}

.footer-rating .stars {
  grid-column: 2;
  grid-row: 1;
}

.footer-rating-total {
  grid-column: 2;
  grid-row: 2;
  font: var(--type-label-xs);
  color: #9aa0a8;
  white-space: nowrap;
  transition: var(--transition-fast);
}

/* The arrow out to Google at the card's right, level with its middle, as
   the Instagram card's is, and a step smaller, as the card is (Alban, 27
   September 2026: it sat small at the end of the line of reviews). */
.footer-rating-go {
  grid-column: 3;
  grid-row: 1 / span 2;
  /* 1.25rem tall: an icon is 1.4em */
  font-size: calc(1.25rem / 1.4);
  color: #9aa0a8;
  transition: var(--transition-fast);
}

/* Five stars filled to --rating out of five: a gold bar, faint past the
   rating, cut into stars by a mask of Tabler's filled star. Each
   star has a 1.25em cell, so a whole number of stars ends on a cell's
   edge. */
.stars {
  --rating: 5;
  --star-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8.243 7.34l-6.38 .925l-.113 .023a1 1 0 0 0 -.44 1.684l4.622 4.499l-1.09 6.355l-.013 .11a1 1 0 0 0 1.464 .944l5.706 -3l5.693 3l.1 .046a1 1 0 0 0 1.352 -1.1l-1.091 -6.355l4.624 -4.5l.078 -.085a1 1 0 0 0 -.633 -1.62l-6.38 -.926l-2.852 -5.78a1 1 0 0 0 -1.794 0l-2.853 5.78z'/%3E%3C/svg%3E");
  display: inline-block;
  flex: none;
  width: 6.25em;
  height: 1em;
  font-size: 0.875rem;
  background: linear-gradient(to right, var(--gold) calc(var(--rating) * 20%), rgba(255, 255, 255, 0.2) 0);
  -webkit-mask: var(--star-mask) left center / 1.25em 1em repeat-x;
  mask: var(--star-mask) left center / 1.25em 1em repeat-x;
}

.footer-legal {
  margin: 0.4rem 0 0;
  padding-top: 1.6rem;
  width: 100%;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  font: var(--type-body-xs);
  color: #8b919a;
}

/* Its parts (the firm, where it's registered, its number and its address)
   each stay whole, so a narrow screen wraps the line between them, never
   in the middle of one ("England / & Wales", or the address) */
.footer-legal span {
  white-space: nowrap;
}

/* STACKED (a phone, a tablet): more air between the rows, the links and
   the buttons than the desktop's band needs, so the column doesn't read as
   one crowded block. */
@media (max-width: 1024px) {
  .footer {
    padding: 4rem 0 3rem;
  }

  .footer .container {
    gap: 2.5rem;
  }

  .footer-nav {
    gap: 1.1rem 1.75rem;
  }

  .footer-reach,
  .footer-reach-row {
    gap: 1rem;
  }

  .footer-legal {
    margin-top: 0;
    padding-top: 2.25rem;
  }
}

/* FOOTER ON A DESKTOP: the line on the left, the buttons and the rating on
   the right, and the links in the room between them; the legal line along
   the bottom. */
@media (min-width: 1025px) {
  .footer {
    text-align: left;
    padding: 3.25rem 0 2.25rem;
  }

  .footer .container {
    display: grid;
    grid-template-columns: minmax(14rem, 20rem) minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 3rem;
    row-gap: 2.25rem;
  }

  /* centred in the room between, evenly spaced, in two centred rows where
     one won't fit */
  .footer-nav {
    justify-content: center;
    gap: 0.6rem clamp(1.25rem, 2.2vw, 2.25rem);
  }

  .footer-legal {
    grid-column: 1 / -1;
    margin-top: 0;
  }
}

/* ==========================================
   THE PROJECTS' PAGES
   A page per project (tools/pages.mjs writes them from the home page's
   cards and content/projects.mjs); the header and the footer are the
   home page's own. Where a section opens on a centred title, a project's
   page opens on a head set to the left (the handover of 26 September
   2026): the trail, then the title, a step above a section's
   (--type-page-title), with the first paragraph as a lead under it
   (--type-body-xl) and the place on an outlined card beside them, and
   the project's facts on cards of the same kind in a row under. The rest
   of the write-up reads in one column under them, as wide as the lead,
   with the lead, the facts and the text evenly spaced and no hairline
   between (Alban, 27 September 2026); the pictures follow with no
   heading of their own, and a way back to the portfolio stands under
   them, with a section's room before the footer. The head's row wraps as
   the screen narrows: the title and the lead first, then the facts in
   rows; a phone leaves the place out.
   ========================================== */
.page {
  padding-top: var(--header-h);
  padding-bottom: 6rem;
}

.page-head {
  padding-top: 4.5rem;
}

/* the trail above the title: Home, the portfolio, this project */
.crumbs ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.5rem;
  margin-bottom: 2rem;
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.crumbs li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.crumbs .icon {
  font-size: 0.75rem;
}

/* the title and the lead at the left, the place at the right, the facts
   along the foot: one row that wraps */
.page-intro {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1.5rem 4rem;
}

.page-intro-text {
  flex: 1 1 320px;
  max-width: 52rem;
  display: grid;
  gap: 1.25rem;
}

.page-title {
  font: var(--type-page-title);
  letter-spacing: var(--letter-spacing-tight);
  color: var(--text-dark);
  text-wrap: balance;
}

.page-lead {
  font: var(--type-body-xl);
  color: var(--text-dark);
  text-wrap: pretty;
}

/* The place, and each fact, on an outlined card that hugs its words, its
   icon in red before them; a term only a screen reader hears (.sr-only)
   says what each fact is. The words wrap only where a card on its own is
   wider than the screen (a phone of 320px), never to fit beside another:
   a card that doesn't fit takes the next row whole. */
.page-place,
.page-fact {
  padding: 0.75rem 1.125rem;
  border: 1px solid var(--outline-on-light);
  border-radius: var(--radius);
}

.page-place {
  margin-top: 0.5rem;
}

.page-place,
.page-fact dd {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font: var(--type-heading-md);
  color: var(--text-dark);
}

.page-place .icon,
.page-fact .icon {
  flex: none;
  /* 1.25rem tall: an icon is 1.4em */
  font-size: calc(1.25rem / 1.4);
  color: var(--accent);
}

.page-facts {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

/* A phone, upright or on its side, leaves the place out (Alban, 27
   September 2026): the title names the town already, and upright the
   card stood alone on a row of its own under the lead. And its trail
   stands 2rem under the header, as the title stands under the trail
   (the same day: 4.5rem left a band of empty page under the header). */
@media (max-width: 768px), (max-height: 500px) {
  .page-head {
    padding-top: 2rem;
  }

  .page-place {
    display: none;
  }
}

/* The rest of the write-up under the facts, in one column as wide as the
   lead above it (.page-intro-text), on its left edge: three columns of a
   write-up this short ran two to six lines each (Alban, 26 September
   2026). It stands as far under the facts as they stand under the lead,
   2rem (the head's row gap and the facts' own 0.5rem), so the lead, the
   facts and the text are evenly spaced. */
.page-body {
  padding-top: 2rem;
}

.page-text {
  max-width: 52rem;
}

.page-text p {
  font: var(--type-body-md);
  color: var(--text);
  text-wrap: pretty;
  margin-bottom: 1rem;
}

.page-text p:last-child {
  margin-bottom: 0;
}

.page-pictures,
.page-end {
  padding-top: 4rem;
}

/* A project's pictures, each at its own shape with its label under it,
   in columns like a mosaic, and the columns brought level at the foot
   (script.js, .is-laid): each picture goes to the column that is
   shortest so far, then each column's pictures are cropped by the little
   it takes for every column to end on one line. Three stand in one row,
   centred, each at its own shape, no taller than the screen allows; one
   or two stand at a column's width each, from the left (.is-few), the
   way they would sit in the mosaic; and two or three stack in the one
   column on a phone, where a row of three came out 72px tall. Until the
   script runs, and without it, the columns are the browser's own, as
   many as the script would make. */
.photo-set {
  columns: 3;
  column-gap: var(--card-gap);
}

/* Until the script has laid the set out it is kept out of sight, so the
   browser's own columns never show and then reshuffle into the mosaic
   (on a cold visit the script arrives a moment after the stylesheet).
   The page's head says the script is coming (html.has-script,
   tools/pages.mjs), and should it not lay the set out within two
   seconds, the set shows as the browser laid it. */
html.has-script .photo-set:not(.is-laid) {
  visibility: hidden;
  animation: photo-set-show 0s 2s forwards;
}

@keyframes photo-set-show {
  to { visibility: visible; }
}

@media (max-width: 1024px) {
  .photo-set {
    columns: 2;
  }
}

@media (max-width: 640px) {
  .photo-set {
    columns: 1;
  }
}

.photo-set.is-laid {
  columns: auto;
  display: flex;
  gap: var(--card-gap);
  justify-content: center;
}

.photo-column {
  flex: 1 1 0;
  min-width: 0;
  display: grid;
  gap: var(--card-gap);
  align-content: start;
}

.photo-row {
  display: flex;
  justify-content: center;
  gap: var(--card-gap);
  width: 100%;
}

/* one or two, from the left: alone and centred at three quarters of the
   screen's height, a portrait video stood like a phone in the middle of
   an empty page (Alban, 26 September 2026) */
.photo-row.is-few {
  justify-content: flex-start;
}

/* a picture's card, outlined; a hover turns the outline red, as the
   portfolio's cards do */
.photo {
  break-inside: avoid;
  margin: 0 0 var(--card-gap);
  border: 1px solid var(--outline-on-light);
  border-radius: var(--radius);
  overflow: hidden;
  transition: border-color var(--hover-fade);
}

@media (hover: hover) {
  .photo:hover {
    border-color: var(--accent);
  }
}

.photo-column .photo,
.photo-row .photo {
  margin: 0;
}

.photo-row .photo {
  flex: 0 1 auto;
  min-width: 0;
}

/* The picture's box: a link to the picture's file, which the script makes
   open the picture in the lightbox instead (below); a video's is a plain
   box round its player. It has the soft ground under its picture, which
   shows while the picture is on its way, so an outlined card with its
   caption and a soft box where the picture will be stands in for it (the
   one fill a card on a light ground has, CLAUDE.md). The keyboard's ring
   goes on the whole card, since the box's own, drawn outside it, would be
   clipped at the card's edge. */
.photo-media {
  display: block;
  position: relative;
  overflow: hidden;
  background-color: var(--bg-soft);
  cursor: zoom-in;
  -webkit-tap-highlight-color: transparent;
}

.photo-video .photo-media {
  cursor: auto;
}

.photo-media:focus-visible {
  outline: none;
}

.photo:has(.photo-media:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.photo-media img,
.photo-media video,
.lightbox-player video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A picture fades in over its ground as it arrives: script.js marks the
   ones still to come and takes the mark off on load, so a picture already
   here shows at once, and without the script nothing is ever marked. The
   lightbox's picture does the same over the veil, faded on its box
   (.lightbox-media, below) rather than on its own outlined edge. */
.photo-media img {
  transition: opacity 0.5s var(--ease-out);
}

.photo-media img.is-loading {
  opacity: 0;
}

/* one line under each picture, so the columns' feet can be made level */
.photo figcaption {
  padding: 0.875rem 1rem;
  font: var(--type-body-sm);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ...but in one column (a phone) there are no other feet to line up
   with, and a long caption takes a second line rather than losing its
   end (script.js sets .is-single) */
.photo-set.is-single .photo figcaption {
  white-space: normal;
  text-wrap: pretty;
}

/* The video player: Plyr (assets/plyr, its own stylesheet loaded before
   this one), with the site's colours, corners, type and icons laid over
   it, in a picture's box and in the lightbox alike. The big play button
   is a button of the site's, a white outline and arrow on a white ground
   at 35%, so the button shows on a light still too (Alban, 27 September
   2026: the outline alone was lost on a light one, a dark arrow on 80%
   white wasn't the look, and 35% his pick of 35, 50 and 65); the bar of
   controls is a flat translucent strip; the icons are Tabler's lines
   (the page's sprite carries them under the names Plyr looks for:
   tools/pages.mjs). */
:is(.photo-media, .lightbox-player) .plyr {
  --plyr-color-main: var(--accent-on-dark);
  --plyr-control-radius: var(--radius);
  --plyr-control-icon-size: 20px;
  --plyr-video-control-color: var(--text-white);
  --plyr-video-control-color-hover: var(--text-white);
  /* the hover's ground is a --fill of the site's (below) */
  --plyr-video-control-background-hover: transparent;
  --plyr-video-controls-background: rgba(16, 18, 22, 0.55);
  --plyr-range-fill-background: var(--accent-on-dark);
  --plyr-video-range-track-background: rgba(255, 255, 255, 0.3);
  --plyr-range-thumb-background: var(--text-white);
  --plyr-range-thumb-shadow: none;
  --plyr-range-track-height: 3px;
  --plyr-range-thumb-height: 12px;
  --plyr-focus-visible-color: var(--accent-on-dark);
  --plyr-font-family: var(--font-heading);
  --plyr-font-size-base: var(--font-size-sm);
  --plyr-font-size-small: var(--font-size-xs);
  --plyr-font-size-time: var(--font-size-xs);
  --plyr-font-size-large: var(--font-size-lg);
  --plyr-font-size-xlarge: var(--font-size-xl);
  --plyr-font-size-menu: var(--font-size-sm);
  --plyr-font-size-badge: var(--font-size-xs);
  --plyr-line-height: var(--line-height-normal);
  height: 100%;
  width: 100%;
  /* Plyr's own 200px, wider than a portrait video's box in the lightbox on
     a phone on its side, pushed the player past the box and its full
     screen button out of sight */
  min-width: 0;
}

:is(.photo-media, .lightbox-player) .plyr__poster {
  background-size: cover;
}

:is(.photo-media, .lightbox-player) .plyr__control svg {
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke);
  stroke-linecap: round;
  stroke-linejoin: round;
}

:is(.photo-media, .lightbox-player) .plyr--full-ui.plyr--video .plyr__control--overlaid {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--btn-h);
  height: var(--btn-h);
  padding: 0;
  border: 1px solid var(--text-white);
  border-radius: var(--radius);
  --fill: rgba(255, 255, 255, 0.35);
  background-color: var(--fill);
  color: var(--text-white);
  opacity: 1;
}

/* the arrow's box in the button's middle: Tabler draws it 1.25px right
   of its square's middle at this size, and a nudge of 1px more to the
   right read as off centre (Alban, 27 September 2026) */
:is(.photo-media, .lightbox-player) .plyr__control--overlaid svg {
  left: -1px;
}

/* playing, the big button goes (Plyr's own rule, outranked by the one
   above, said again here) */
:is(.photo-media, .lightbox-player) .plyr--full-ui.plyr--video.plyr--playing .plyr__control--overlaid {
  opacity: 0;
  visibility: hidden;
}

/* a video that hasn't been played, or has ended, shows its still and the
   big button alone: the bar of controls only once it's playing or paused,
   and out of the keyboard's reach till then too (hidden, not only
   see-through: three unseen stops for Tab before the big button) */
:is(.photo-media, .lightbox-player) .plyr--stopped .plyr__controls {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* Plyr's controls fade `all` (their ground among it) and CLAUDE.md fades
   a fill as --fill: a control's hover ground is a --fill here, and they
   take the site's own transitions */
:is(.photo-media, .lightbox-player) .plyr__control {
  --fill: transparent;
  background-color: var(--fill);
  transition: var(--transition-fast);
}

@media (hover: hover) {
  :is(.photo-media, .lightbox-player) .plyr--video .plyr__controls .plyr__control:hover {
    --fill: rgba(255, 255, 255, 0.15);
    background-color: var(--fill);
  }
}

:is(.photo-media, .lightbox-player) .plyr--video .plyr__controls {
  padding-top: 10px;
}

:is(.photo-media, .lightbox-player) .plyr--full-ui input[type="range"]::-webkit-slider-thumb {
  border-radius: var(--radius);
}

:is(.photo-media, .lightbox-player) .plyr--full-ui input[type="range"]::-moz-range-thumb {
  border-radius: var(--radius);
}

@media (hover: hover) {
  :is(.photo-media, .lightbox-player) .plyr--full-ui.plyr--video .plyr__control--overlaid:hover {
    border-color: var(--accent-on-dark);
  }
}

/* the buttons at the foot: the way back on the left, a quote and contact
   on the right; on a phone, one under another */
.page-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
}

.page-actions-right {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

@media (max-width: 640px) {
  .page-actions,
  .page-actions-right {
    flex-direction: column;
  }

  .page-actions .btn {
    width: 100%;
  }
}

/* ==========================================
   THE LIGHTBOX
   A picture on its own over a white veil across the whole screen, all
   but solid (script.js opens it from a picture's link and steps through
   the set; the box itself is in the page's markup, tools/pages.mjs). The
   picture keeps its shape, no taller than the screen less the room
   round it (--lightbox-room: the padding above and below, the caption
   and its gap), with its caption under it in the caption's own grey;
   the cross at the top right and the arrows at the sides are the site's
   icon buttons as they are on a light ground. On a phone the picture
   takes the width and the arrows come down into a row under the
   caption, off the picture; a swipe steps too. A video plays there in a
   player of its own (.lightbox-player, in the player's dress above),
   sized from its shape (--ratio, set by the script). Pinned over
   everything, it is white like the header, so Safari's strip behind the
   status bar stays as it is while it is open (styles.css, the header);
   shut, it takes visibility: hidden as well as fading out, so Safari
   never paints it as a bar under its controls (CLAUDE.md, "Design
   rules").
   ========================================== */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: calc(5rem + env(safe-area-inset-top)) calc(5.5rem + env(safe-area-inset-right)) calc(3rem + env(safe-area-inset-bottom)) calc(5.5rem + env(safe-area-inset-left));
  --lightbox-room: calc(11rem + env(safe-area-inset-top) + env(safe-area-inset-bottom));
  /* white, all but solid */
  background-color: rgba(255, 255, 255, 0.94);
  color: var(--text-dark);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s var(--ease-out), visibility 0s linear 0.25s;
  /* a swipe across it is the script's, a step; a pinch stays the browser's */
  touch-action: pan-y pinch-zoom;
  -webkit-tap-highlight-color: transparent;
}

.lightbox.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.25s var(--ease-out), visibility 0s;
}

.lightbox-figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 100%;
  margin: 0;
}

.lightbox-media {
  display: flex;
  justify-content: center;
  max-width: 100%;
  /* The picture fades in here as it arrives (script.js marks it), not on
     its own outlined edge: WebKit draws a fading box as a layer snapped to
     whole pixels, which cuts into an outline on its edge (CLAUDE.md), so
     this box keeps 2px round the picture, taken back by its margin. */
  padding: 2px;
  margin: -2px;
  transition: opacity 0.5s var(--ease-out);
}

.lightbox-media:has(> .lightbox-picture.is-loading) {
  opacity: 0;
}

/* the picture at its own shape, within the width and the height left;
   dvh is the screen as it is, with a phone's bars showing or not */
.lightbox-picture {
  max-width: 100%;
  max-height: calc(100vh - var(--lightbox-room));
  max-height: calc(100dvh - var(--lightbox-room));
  width: auto;
  height: auto;
  object-fit: contain;
  border: 1px solid var(--outline-on-light);
  border-radius: var(--radius);
}

/* a video's player, a box of the video's shape within the same room */
.lightbox-player {
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--ratio);
  width: min(100%, calc((100vh - var(--lightbox-room)) * var(--ratio)));
  width: min(100%, calc((100dvh - var(--lightbox-room)) * var(--ratio)));
  border: 1px solid var(--outline-on-light);
  border-radius: var(--radius);
}

/* the picture's caption, on one line under it */
.lightbox-caption {
  margin-top: 1rem;
  font: var(--type-body-sm);
  color: var(--text-muted);
  text-align: center;
}

/* the buttons stand out of the flow, at the corner and the sides: on a
   desktop the arrows' box is no box at all, so they place themselves
   against the veil */
.lightbox-nav {
  display: contents;
}

.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: absolute;
}

.lightbox-close {
  top: calc(1.5rem + env(safe-area-inset-top));
  right: calc(1.5rem + env(safe-area-inset-right));
}

.lightbox-prev,
.lightbox-next {
  top: calc(50% - var(--btn-h) / 2);
}

.lightbox-prev {
  left: calc(1.5rem + env(safe-area-inset-left));
}

.lightbox-next {
  right: calc(1.5rem + env(safe-area-inset-right));
}

/* a project of one picture has nowhere to step */
.lightbox.is-alone .lightbox-nav {
  display: none;
}

/* on a phone the picture takes the width, less the page's margin, and
   the arrows come down into a row under the caption */
@media (max-width: 640px) {
  .lightbox {
    padding-right: max(var(--gutter), env(safe-area-inset-right));
    padding-bottom: calc(2rem + env(safe-area-inset-bottom));
    padding-left: max(var(--gutter), env(safe-area-inset-left));
    --lightbox-room: calc(14rem + env(safe-area-inset-top) + env(safe-area-inset-bottom));
  }

  .lightbox-nav {
    display: flex;
    justify-content: center;
    gap: 1rem;
    margin-top: 1rem;
  }

  .lightbox-prev,
  .lightbox-next {
    position: static;
  }
}

/* a phone on its side is short: less room under the picture, and the
   arrows back at the sides, where there is width to spare */
@media (max-height: 500px) {
  .lightbox {
    padding: calc(5rem + env(safe-area-inset-top)) calc(5.5rem + env(safe-area-inset-right)) calc(1.5rem + env(safe-area-inset-bottom)) calc(5.5rem + env(safe-area-inset-left));
    --lightbox-room: calc(9.5rem + env(safe-area-inset-top) + env(safe-area-inset-bottom));
  }

  .lightbox-nav {
    display: contents;
  }

  .lightbox-prev,
  .lightbox-next {
    position: absolute;
  }
}

/* opened at one of its sections, a page lands on it at once (index.html
   sets the class ahead of the body, script.js takes it off once the page
   has settled) */
html.is-landing {
  scroll-behavior: auto;
}
