/* ==========================================================================
   GEZZA — Masonry & Restoration
   Single stylesheet. Mobile-first. No framework, no build step.

   CONCEPT: COURSING
   Masonry has one organising principle — the course. Units of fixed height
   stacked in horizontal bands, with joints deliberately offset so no two
   adjacent courses align. Aligned joints make a weak wall.

   So the page is built as courses: bands of deliberate density separated by
   mortar joints, with alternating courses shifting their content column so
   the page's vertical joints never stack. Squared arrises throughout — brick
   has no border-radius. No shadows; masonry sits, it does not float.

   MATERIALS
   Wet slate, lime mortar, and Ontario buff brick — the pale yellow "white
   brick" fired from local high-lime clay that Kitchener-Waterloo's Victorian
   building stock is largely built from.

   TYPE — two families, one job each.
   Fraunces carries every display line: a Windsor-descended serif with sharp
   terminals (SOFT 0) and a true optical-size axis, so it grows sturdier
   rather than thinner as it scales. It is the lettering cut into a
   building's datestone. Archivo carries everything a person reads or clicks:
   a signage grotesque with a width axis, set at 100% for text and expanded
   for small structural type. The change of family IS the hierarchy boundary
   — serif above it, grotesque below — so a heading never needs a colour or
   an all-caps label to read as one.

   MOTION
   One orchestrated page-load sequence in the masthead, and one quiet reveal
   as sections arrive. Both use the same easing and the same distance, so
   every page moves identically. Everything else responds to the visitor:
   hover, focus, open. All of it collapses under prefers-reduced-motion.
   ========================================================================== */

/* --------------------------------------------------------------- tokens -- */

:root {
  /* Fields. Slate and lime carry ~90% of the surface. */
  --slate: #23262b;        /* dark course — cool, wet concrete               */
  --slate-deep: #191c20;   /* footer, one shade down                        */
  --slate-veil: #2b2f35;   /* raised surface on the dark course             */
  --lime: #e9e8e1;         /* light course — mineral grey-green, not cream  */
  --lime-raised: #f0efe9;  /* raised surfaces on the light course           */

  /* Text. Both carried over from the client's logo SVGs. */
  --ink: #1f1f1c;          /* primary text                  13.45:1 on lime */
  --stone: #5f5a54;        /* secondary text + input borders 5.56:1 on lime */

  /* Structure. */
  --mortar: #c9c8be;       /* joints and decorative rules ONLY — never text */

  /* Accent. Two values: one accent cannot serve both fields accessibly.
     --buff is 1.89:1 on lime, so it is BANNED for text on the light course. */
  --buff: #c6a662;         /* accent, dark fields only     6.53:1 on slate  */
  --buff-fired: #7e6024;   /* accent, light fields only    4.77:1 on lime   */

  /* Kiln variation across a fired batch. 1.21:1 total spread — reads as
     material, not as hierarchy. Every tone carries --ink at 7.8:1 or better. */
  --brick-1: #cdbc96;
  --brick-2: #c7b48c;
  --brick-3: #d2c2a0;
  --brick-4: #c4b089;
  --brick-5: #cbb891;
  --brick-6: #d0be9b;

  /* Placeholder flags. Deliberately foreign so they cannot ship by accident. */
  --flag-bg: #fff3cd;
  --flag-text: #5c4405;
  --flag-line: #997404;

  --font-display: "Fraunces", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --font: "Archivo", "Helvetica Neue", Helvetica, sans-serif;

  /* Sharp terminals, standard letterforms. The display face is cut, not drawn. */
  --display-axes: "SOFT" 0, "WONK" 0;

  --step--1: clamp(0.8125rem, 0.78rem + 0.16vw, 0.9rem);
  --step-0: clamp(1rem, 0.96rem + 0.2vw, 1.0938rem);
  --step-1: clamp(1.15rem, 1.07rem + 0.36vw, 1.375rem);
  --step-2: clamp(1.45rem, 1.25rem + 0.95vw, 2.1rem);
  --step-3: clamp(1.95rem, 1.5rem + 2.1vw, 3.35rem);
  --step-4: clamp(2.6rem, 1.55rem + 4.6vw, 5.1rem);

  --space-2xs: 0.375rem;
  --space-xs: 0.625rem;
  --space-sm: 1rem;
  --space-md: 1.75rem;
  --space-lg: 2.25rem;
  --space-xl: 3.5rem;

  --joint: 6px;                     /* the mortar gap between brick units   */
  --course-pad: clamp(2.75rem, 1.6rem + 3.4vw, 4.75rem);
  --container: 1240px;
  --gutter: clamp(1rem, 0.4rem + 3vw, 2.75rem);

  --measure: 66ch;
  --ease: cubic-bezier(0.2, 0.6, 0.25, 1);

  /* One distance and one duration for every reveal on the site. */
  --rise: 1.35rem;
  --reveal-ms: 680ms;
}

/* --------------------------------------------------------------- reset --- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Keeps a keyboard-focused element, or an anchor target, out from under the
     sticky header (WCAG 2.4.11). The header is about 5.4rem tall at rest. */
  scroll-padding-top: 6rem;
}

body {
  margin: 0;
  background: var(--lime);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--step-0);
  font-weight: 400;
  font-stretch: 100%;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

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

img {
  height: auto;
}

/* Display type: Fraunces, optical sizing on, set tight. Never bolder than
   600 — at these sizes the optical-size axis supplies the weight. */
h1,
h2 {
  margin: 0 0 var(--space-md);
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-variation-settings: var(--display-axes);
  font-weight: 600;
  line-height: 1.02;
  letter-spacing: -0.018em;
  text-wrap: balance;
}

h1 {
  font-size: var(--step-3);
  line-height: 0.98;
}

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

/* Below the serif/grotesque boundary. The change of family is the rank. */
h3 {
  margin: 0 0 var(--space-xs);
  font-family: var(--font);
  font-size: var(--step-1);
  font-weight: 700;
  font-stretch: 104%;
  line-height: 1.25;
  letter-spacing: -0.005em;
}

p {
  margin: 0 0 var(--space-md);
  max-width: var(--measure);
  text-wrap: pretty;
}

p:last-child,
ul:last-child,
dl:last-child {
  margin-bottom: 0;
}

a {
  color: inherit;
}

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

.course--dark :focus-visible,
.site-footer :focus-visible,
.masthead :focus-visible,
.site-header :focus-visible {
  outline-color: var(--buff);
}

/* -------------------------------------------------------------- courses -- */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.course {
  padding-block: var(--course-pad);
}

.course--light {
  background: var(--lime);
  color: var(--ink);
}

.course--dark {
  background: var(--slate);
  color: var(--lime);
}

.course--short {
  padding-block: clamp(2rem, 1.2rem + 2.6vw, 3.25rem);
}

/* A mortar joint between two adjacent light courses. Decorative separator
   only — never an interactive border (1.37:1 on lime). */
.course--light + .course--light {
  border-top: 2px solid var(--mortar);
}

.course--dark p,
.site-footer p,
.masthead p {
  color: color-mix(in srgb, var(--lime) 84%, transparent);
}

/* A course can shift its content column one of twelve. No page uses it now;
   every course runs full container width so the edges line up. */
@media (min-width: 62rem) {
  .course--shift .course__body {
    margin-left: 8.333%; /* one column of twelve */
  }
}

/* Every course opens with a struck joint above its heading — the mark that a
   new course begins. It draws itself as the heading arrives. */
.course h2::before {
  content: "";
  display: block;
  width: 3.25rem;
  height: 3px;
  margin-bottom: var(--space-sm);
  background: var(--buff-fired);
  transform-origin: left center;
}

.course--dark h2::before {
  background: var(--buff);
}

/* Two places where the joint would be redundant or invisible. */
.form-aside h2::before,
.course h2.visually-hidden::before {
  display: none;
}

.course__lede {
  font-size: var(--step-1);
  max-width: 52ch;
}

.course--dark .course__lede {
  color: color-mix(in srgb, var(--lime) 90%, transparent);
}

/* A modest real heading that cedes the visual lead to the element below it.
   Sentence case, normal tracking — not a tracked-out label. */
.course__kicker {
  margin-bottom: var(--space-sm);
  font-size: var(--step-1);
  letter-spacing: -0.01em;
  line-height: 1.3;
}

/* ------------------------------------------------------------- utilities -- */

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

.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  padding: var(--space-sm) var(--space-md);
  background: var(--buff);
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-120%);
}

.skip-link:focus {
  transform: translateY(0);
}

/* Placeholder flags for content the client has not supplied. Deliberately
   jarring — see README "Before launch". */
.ph {
  background: var(--flag-bg);
  color: var(--flag-text);
  border: 1px dashed var(--flag-line);
  border-radius: 2px;
  padding: 0.05em 0.4em;
  font-size: 0.92em;
  font-weight: 600;
  white-space: nowrap;
}

.ph-block {
  border: 2px dashed var(--flag-line);
  border-radius: 2px;
  background: var(--flag-bg);
  color: var(--flag-text);
  padding: var(--space-sm);
  font-size: var(--step--1);
  font-weight: 600;
  max-width: none;
}

/* -------------------------------------------------------------- actions -- */

/* Squared, like a stamped plate. No pills, no radius. The hover is a fill
   struck across the face from the left, the way mortar is drawn along a
   joint — a wipe, not a colour swap. */
.btn {
  position: relative;
  z-index: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: 0.85em 1.6em;
  border: 2px solid transparent;
  font: inherit;
  font-weight: 600;
  font-stretch: 106%;
  line-height: 1.2;
  letter-spacing: 0.005em;
  text-decoration: none;
  cursor: pointer;
  transition: color 220ms var(--ease), border-color 220ms var(--ease);
}

.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--btn-sweep, transparent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 300ms var(--ease);
}

.btn:hover::after,
.btn:focus-visible::after {
  transform: scaleX(1);
}

.btn--primary {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--lime);
  --btn-sweep: var(--buff-fired);
}

.btn--primary:hover {
  border-color: var(--buff-fired);
}

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

.btn--ghost:hover {
  border-color: var(--ink);
  color: var(--lime);
}

/* On any dark field the primary button inverts: buff plate, ink lettering.
   Without the header in this list its CTA is ink-on-slate and stops reading
   as a button at all. */
.course--dark .btn--primary,
.site-header .btn--primary,
.masthead .btn--primary {
  background: var(--buff);
  border-color: var(--buff);
  color: var(--ink);
  --btn-sweep: var(--lime);
}

.course--dark .btn--primary:hover,
.site-header .btn--primary:hover,
.masthead .btn--primary:hover {
  border-color: var(--lime);
}

.course--dark .btn--ghost,
.site-header .btn--ghost,
.masthead .btn--ghost {
  --btn-sweep: var(--lime);
}

.course--dark .btn--ghost:hover,
.site-header .btn--ghost:hover,
.masthead .btn--ghost:hover {
  border-color: var(--lime);
  color: var(--ink);
}

/* The disabled call button is a placeholder, not an offer. It must not
   pretend to be pressable. */
.btn[aria-disabled="true"] {
  cursor: default;
  opacity: 0.85;
  /* No sweep here, so the overflow clip that contains it is not needed - and
     without lifting it the placeholder chip gets cut off at the border. */
  overflow: visible;
}

.btn[aria-disabled="true"]::after {
  content: none;
}

/* With no sweep to sit behind it, the ghost hover's ink lettering lands on the
   slate and vanishes. A placeholder does nothing on hover, so it keeps exactly
   the colour and border it has at rest. Once the number is real and this becomes
   a link, the aria-disabled rules stop matching and the normal sweep applies. */
.btn[aria-disabled="true"]:hover {
  border-color: currentColor;
  color: inherit;
}

.btn .ph {
  margin-left: 0.5em;
}

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

/* Links are underlined and thicken on hover. No appended arrows. */
.tlink {
  font-weight: 600;
  color: var(--buff-fired);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.22em;
  transition: text-decoration-thickness 160ms var(--ease),
    text-underline-offset 160ms var(--ease);
}

.tlink:hover {
  text-decoration-thickness: 4px;
  text-underline-offset: 0.3em;
}

.course--dark .tlink,
.site-footer .tlink {
  color: var(--buff);
}

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

/* The header settles as you scroll: the plate tightens (smaller logo, less
   padding) and a hairline joint appears beneath it.

   THE TIGHTENING MUST NEVER MOVE THE PAGE. With JavaScript on, the header is
   fixed, out of the page flow, and `body` carries a constant spacer the height of
   the header at rest. An earlier version shrank a sticky header that was still in
   the flow: the content below jumped up, which pushed the scroll position back
   across the 8px threshold, and the bar flickered. Without JavaScript it stays a
   plain sticky bar that never changes size.

   The heights are explicit (logo height plus padding), not measured, so the
   spacer and the header agree exactly. */
:root {
  --logo-h: clamp(2.95rem, 5.5vw, 3.875rem);
  --header-pad: 0.75rem;
  --header-h: calc(var(--logo-h) + 2 * var(--header-pad) + 1px);
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--slate);
  color: var(--lime);
  border-bottom: 1px solid transparent;
  transition: border-color 260ms var(--ease), background 260ms var(--ease);
}

.js .site-header {
  position: fixed;
  inset: 0 0 auto 0;
}

/* The spacer. It reads --header-h from :root, which keeps the at-rest values
   even while the header below overrides them. */
.js body {
  padding-top: var(--header-h);
}

.site-header.is-stuck {
  --logo-h: clamp(2.55rem, 4.25vw, 3.125rem);
  --header-pad: 0.55rem;
  background: color-mix(in srgb, var(--slate) 94%, black);
  border-bottom-color: color-mix(in srgb, var(--lime) 20%, transparent);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  height: calc(var(--logo-h) + 2 * var(--header-pad));
  transition: height 260ms var(--ease);
}

.site-logo {
  display: block;
  flex-shrink: 0;
}

.site-header .site-logo img {
  width: auto;
  height: var(--logo-h);
  transition: height 260ms var(--ease);
}

.nav-toggle {
  display: none; /* only useful once main.js is running; see .js below */
  align-items: center;
  gap: 0.6em;
  padding: 0.55em 0.85em;
  background: transparent;
  border: 2px solid color-mix(in srgb, var(--lime) 32%, transparent);
  font: inherit;
  font-weight: 600;
  color: inherit;
  cursor: pointer;
  transition: border-color 200ms var(--ease);
}

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

.nav-toggle:hover {
  border-color: var(--buff);
}

.nav-toggle__bars {
  display: grid;
  gap: 3px;
  width: 16px;
}

.nav-toggle__bars span {
  display: block;
  height: 2px;
  background: currentColor;
  transition: transform 240ms var(--ease), opacity 180ms var(--ease);
}

/* Open folds the three bars into a cross — motion that reports what the
   button just did. */
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:nth-child(1) {
  transform: translateY(5px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:nth-child(2) {
  opacity: 0;
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:nth-child(3) {
  transform: translateY(-5px) rotate(-45deg);
}

/* On a phone the menu is a drawer hung from the bottom of the header bar, not
   a third item in the header's row. The bar keeps its own height whether the
   menu is open or shut; the drawer overlays the page beneath it. */
/* Hidden behind `.js` so that with JavaScript off the links simply show, stacked
   under the logo, instead of vanishing behind a toggle that cannot open. */
.js .site-nav {
  display: none;
}

@media (max-width: 55.99rem) {
  html:not(.js) .site-header {
    position: static;
  }

  html:not(.js) .site-header__inner {
    flex-wrap: wrap;
  }

  html:not(.js) .site-nav {
    flex: 1 0 100%;
  }
}

.site-nav[data-open="true"] {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  display: block;
  max-height: calc(100vh - 100%);
  max-height: calc(100dvh - 100%);
  overflow-y: auto;
  padding: var(--space-2xs) var(--gutter) var(--space-md);
  background: var(--slate-deep);
  border-top: 1px solid color-mix(in srgb, var(--lime) 18%, transparent);
  border-bottom: 4px solid var(--buff);
  animation: drawer-down 260ms var(--ease) both;
}

/* The drawer is let down from the header edge, the way a hoarding panel drops. */
@keyframes drawer-down {
  from { opacity: 0; clip-path: inset(0 0 100% 0); }
  to { opacity: 1; clip-path: inset(0 0 0 0); }
}

.site-nav__list {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.site-nav__list a {
  text-decoration: none;
}

.site-nav__list a:not(.btn) {
  display: block;
  padding: 0.8em 0;
  font-size: var(--step-1);
  font-weight: 500;
  border-bottom: 1px solid color-mix(in srgb, var(--lime) 18%, transparent);
}

.site-nav__list a[aria-current="page"] {
  color: var(--buff);
}

.site-nav__list li:last-child {
  margin-top: var(--space-md);
}

.site-nav .btn {
  width: 100%;
  font-size: var(--step-0);
}

@media (min-width: 56rem) {
  .nav-toggle,
  .js .nav-toggle {
    display: none;
  }

  .site-nav,
  .site-nav[data-open="true"],
  .site-nav[data-open="false"] {
    position: static;
    display: block;
    max-height: none;
    overflow: visible;
    padding: 0;
    background: none;
    border: 0;
    animation: none;
  }

  .site-nav__list {
    flex-direction: row;
    align-items: center;
    gap: var(--space-md);
  }

  /* The underline is struck from the left rather than switched on. */
  .site-nav__list a:not(.btn) {
    position: relative;
    padding: 0.25em 0;
    font-size: inherit;
    border-bottom: 0;
  }

  .site-nav__list li:last-child {
    margin-top: 0;
  }

  .site-nav__list a:not(.btn)::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -0.15em;
    height: 2px;
    background: var(--buff);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 240ms var(--ease);
  }

  .site-nav__list a:not(.btn):hover::after,
  .site-nav__list a:not(.btn)[aria-current="page"]::after {
    transform: scaleX(1);
  }

  .site-nav .btn {
    width: auto;
    padding: 0.6em 1.15em;
    font-size: inherit;
  }
}

/* ------------------------------------------------------------- masthead -- */

/* Every page opens the same way: a slate plate, the title set large in the
   left six columns, and a ruled plinth underneath. On the four interior pages
   the right five columns hold the page's opening sentence. On the home page
   there is no second column: the photograph is the plate itself, set behind
   the type, and a slate scrim keeps the lettering legible over it. Same grid,
   same joint, same entrance. */
.masthead {
  position: relative;
  background: var(--slate);
  color: var(--lime);
  padding-top: clamp(2rem, 1.2rem + 3.4vw, 3.75rem);
}

/* The struck joint sits on its own element so the entrance draws the rule
   rather than scaling the lettering with it. */
.masthead::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 2; /* above the home page's photograph */
  height: 5px;
  background: var(--buff);
  transform-origin: left center;
}

.masthead__grid {
  display: grid;
  gap: var(--space-lg);
  padding-bottom: clamp(2rem, 1.2rem + 2.8vw, 3.25rem);
}

@media (min-width: 62rem) {
  .masthead__grid {
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--gutter);
    align-items: end;
  }

  .masthead__lead {
    grid-column: 1 / span 6;
  }

  .masthead__aside {
    grid-column: 8 / span 5;
  }
}

.masthead__title {
  display: grid;
  margin-bottom: var(--space-md);
  max-width: 16ch;
}

/* On the pages where the title is the whole left column, drop its trailing
   space so it bottom-aligns cleanly with the sentence beside it. */
.masthead__lead > .masthead__title:last-child {
  margin-bottom: 0;
}

/* The display line and its qualifying clause are separated by size, family
   and colour weight — a distinct clause, not one accented word in a
   headline. */
.masthead__display {
  font-size: var(--step-4);
  line-height: 0.94;
  letter-spacing: -0.028em;
}

/* The home title is the largest type on the site; the other three sit one
   step down. Same construction, ranked — not a different design per page. */
.masthead:not(.masthead--home) .masthead__title {
  max-width: 22ch;
}

.masthead:not(.masthead--home) .masthead__display {
  font-size: var(--step-3);
  line-height: 0.98;
  letter-spacing: -0.022em;
}

.masthead__sub {
  margin-top: 0.5em;
  font-family: var(--font);
  font-size: clamp(1rem, 0.72rem + 1.1vw, 1.5rem);
  font-weight: 500;
  font-stretch: 100%;
  line-height: 1.2;
  letter-spacing: 0;
  color: color-mix(in srgb, var(--lime) 76%, transparent);
}

.masthead__lede {
  max-width: 44ch;
  font-size: var(--step-1);
}

/* On the interior pages the lede sits under the title as a supporting line, so it
   drops to body size; the home page keeps the larger one over the photograph. */
.masthead:not(.masthead--home) .masthead__lede {
  font-size: var(--step-0);
}

.masthead__aside {
  max-width: 46ch;
  font-size: var(--step-1);
  line-height: 1.5;
}

@media (min-width: 62rem) {
  .masthead__aside {
    padding-bottom: 0.4em;
    padding-left: var(--space-md);
    border-left: 3px solid var(--buff);
  }
}

/* HOME ONLY. The photograph fills the plate behind the type. The stage
   carries the padding the masthead gives up, so the picture runs from under the
   struck joint to the plinth. It is a real <img>, not a CSS background, so it
   can be sized by srcset and prioritised as the page's largest paint. */
.masthead--home {
  padding-top: 0;
}

.masthead__stage {
  position: relative;
  display: grid;
  align-items: end;
  min-height: clamp(30rem, 72vh, 42rem);
  padding-top: clamp(2rem, 1.2rem + 3.4vw, 3.75rem);
  overflow: hidden;
}

.masthead__backdrop {
  position: absolute;
  inset: 0;
  background: var(--slate-veil); /* the plate, until the photograph arrives */
}

.masthead__backdrop picture,
.masthead__backdrop img {
  width: 100%;
  height: 100%;
}

.masthead__backdrop img {
  object-fit: cover;
  object-position: 88% 30%; /* keep the hand and trowel in frame */
}

/* The scrim is the site's own slate, not a black wash. On a phone the type runs
   the full width, so it darkens from the top down to where the type sits. From
   the wide breakpoint the type holds the left half and the scrim clears toward
   the right, so the trowel stays visible. Worst-case lettering contrast over
   the photograph is checked in docs/HTML-NOTES.md. */
.masthead__backdrop::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--slate) 55%, transparent) 0%,
    color-mix(in srgb, var(--slate) 88%, transparent) 50%,
    color-mix(in srgb, var(--slate) 94%, transparent) 100%
  );
}

@media (min-width: 62rem) {
  .masthead__backdrop img {
    object-position: 74% 50%;
  }

  .masthead__backdrop::after {
    background: linear-gradient(
      90deg,
      color-mix(in srgb, var(--slate) 94%, transparent) 0%,
      color-mix(in srgb, var(--slate) 88%, transparent) 48%,
      color-mix(in srgb, var(--slate) 50%, transparent) 72%,
      color-mix(in srgb, var(--slate) 28%, transparent) 100%
    );
  }
}

.masthead__stage > .container {
  position: relative;
}

/* The plinth: the one fact every page needs to state, on a ruled strip at
   the foot of the masthead. */
.plinth {
  border-top: 1px solid color-mix(in srgb, var(--lime) 22%, transparent);
  background: color-mix(in srgb, var(--slate) 92%, black);
}

.plinth__line {
  margin: 0;
  padding-block: var(--space-sm);
  max-width: none;
  font-size: var(--step--1);
  font-weight: 500;
  font-stretch: 104%;
}

/* ------------------------------------------------------- the brick wall -- */

/* Six services, six units, laid in genuine running bond on a 12-column grid.
   Joints fall at columns 7, 8 and 6 — no two adjacent courses align, which is
   the actual structural rule. Widths encode real weight: foundation cracks
   take 8 columns because that is the highest-stakes problem a homeowner
   brings; the rest sit between 4 and 7. */
.bond {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--joint);
  margin: 0;
  padding: 0;
  list-style: none;
}

.bond__unit {
  display: flex;
}

.unit {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  width: 100%;
  padding: var(--space-md);
  color: var(--ink);
  text-decoration: none;
  transition: filter 240ms var(--ease);
}

/* The joint is struck along the bottom edge, from the left, on hover. */
.unit::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 4px;
  background: var(--buff-fired);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 320ms var(--ease);
}

.unit:hover,
.unit:focus-visible {
  filter: brightness(0.94);
}

.unit:hover::after,
.unit:focus-visible::after {
  transform: scaleX(1);
}

.unit__name {
  margin: 0;
  font-size: var(--step-1);
  font-weight: 700;
  font-stretch: 104%;
}

.unit__desc {
  margin: 0;
  max-width: 46ch;
  font-size: var(--step--1);
  line-height: 1.55;
  color: color-mix(in srgb, var(--ink) 82%, transparent);
}

.bond__unit:nth-child(1) .unit { background: var(--brick-1); }
.bond__unit:nth-child(2) .unit { background: var(--brick-2); }
.bond__unit:nth-child(3) .unit { background: var(--brick-3); }
.bond__unit:nth-child(4) .unit { background: var(--brick-4); }
.bond__unit:nth-child(5) .unit { background: var(--brick-5); }
.bond__unit:nth-child(6) .unit { background: var(--brick-6); }

/* Below the bond breakpoint the wall becomes a soldier course: full-width
   units on end, joints preserved. */
@media (min-width: 56rem) {
  .bond {
    grid-template-columns: repeat(12, 1fr);
  }

  .bond__unit:nth-child(1) { grid-column: span 7; }  /* joint at 7 */
  .bond__unit:nth-child(2) { grid-column: span 5; }
  .bond__unit:nth-child(3) { grid-column: span 8; }  /* joint at 8 */
  .bond__unit:nth-child(4) { grid-column: span 4; }
  .bond__unit:nth-child(5) { grid-column: span 6; }  /* joint at 6 */
  .bond__unit:nth-child(6) { grid-column: span 6; }

  .unit {
    padding: var(--space-lg) var(--space-md);
  }
}

/* ------------------------------------------------------------ spec list -- */

/* Not three equal cards. A ruled specification list, full measure. */
.spec {
  margin: 0;
  padding: 0;
  border-top: 2px solid var(--mortar);
}

.course--dark .spec,
.course--dark .spec__row {
  border-color: color-mix(in srgb, var(--lime) 24%, transparent);
}

.spec__row {
  display: grid;
  gap: var(--space-2xs);
  padding-block: 1.15rem;
  border-bottom: 2px solid var(--mortar);
}

.spec__row dt {
  font-size: var(--step-1);
  font-weight: 700;
  font-stretch: 106%;
  line-height: 1.25;
}

.spec__row dd {
  margin: 0;
  max-width: 52ch;
}

.spec__row dt {
  text-wrap: balance;
}

@media (min-width: 48rem) {
  .spec__row {
    grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
    gap: var(--space-lg);
    align-items: baseline;
  }
}

/* ---------------------------------------------------------------- about -- */

/* The story spans the full container, the same width as the two team photos
   below it, so it lifts the global paragraph measure. */
.story p {
  max-width: none;
}

/* Text left, photograph right, once there is room for both. Only when a photograph
   is present: without one the text runs in a single column. */
.story {
  display: grid;
  gap: var(--space-lg);
}

@media (min-width: 62rem) {
  .story:has(> .story__photo) {
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
    gap: var(--space-xl);
    align-items: start;
  }
}

/* Two people, two columns. Each photograph sits above the name. */
.team {
  display: grid;
  gap: var(--space-lg);
}

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

.story__photo,
.person__photo {
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  margin-bottom: var(--space-sm);
  background: color-mix(in srgb, var(--mortar) 45%, var(--lime));
  overflow: hidden;
}

.story__photo {
  aspect-ratio: 4 / 5;
  margin-bottom: 0;
}

.person__photo {
  aspect-ratio: 3 / 4;
}

.story__photo img,
.person__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.person h3 {
  font-size: var(--step-1);
  font-weight: 700;
  font-stretch: 106%;
  line-height: 1.25;
  text-wrap: balance;
}

.person p {
  max-width: 52ch;
}

/* ------------------------------------------------------------- our work -- */

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

@media (min-width: 48rem) {
  .work {
    grid-template-columns: repeat(12, 1fr);
  }

  /* Laid in bond like the services wall: joints at 7 then 5, never stacked. */
  .work__item:nth-child(1) { grid-column: span 7; }
  .work__item:nth-child(2) { grid-column: span 5; }
  .work__item:nth-child(3) { grid-column: span 5; }
  .work__item:nth-child(4) { grid-column: span 7; }
}

.work__item figure {
  margin: 0;
}

.work__frame {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 10;
  max-width: 100%;
  background: color-mix(in srgb, var(--mortar) 45%, var(--lime));
  overflow: hidden;
}

.work__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 700ms var(--ease);
}

/* Once the grid is in bond, units in a course share a height and differ only
   in length. This must come after the .work__frame rule above: same
   specificity, so source order decides which aspect-ratio wins. */
@media (min-width: 48rem) {
  .work__frame {
    aspect-ratio: auto;
    height: clamp(12rem, 18vw, 17rem);
  }
}

.work__item figure:hover .work__frame img {
  transform: scale(1.03);
}

.work__item figcaption {
  margin-top: var(--space-xs);
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--stone);
}

.course--dark .work__item figcaption {
  color: color-mix(in srgb, var(--lime) 76%, transparent);
}

.work__more {
  margin: var(--space-md) 0 0;
}

/* -------------------------------------------------------- project (work) -- */

/* One project on the Our Work page: a before and after pair, then a ruled
   list of location, problem, work and method. Every project is built the same
   way, so a new one is a copy of an existing section. */
.project__ba {
  display: grid;
  gap: var(--space-md);
  margin-block: var(--space-md) var(--space-lg);
}

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

.project__shot {
  margin: 0;
}

.project__shot figcaption {
  margin-top: var(--space-xs);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--stone);
}

.project .spec__row .ph-block {
  margin: 0;
}

/* ------------------------------------------------------------ datestone -- */

/* The three cities set as a lockup — the way a place name is cut into a
   building's foundation stone. Replaces three identical city cards. */
.datestone {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1em;
  max-width: none;
  margin: 0 0 var(--space-md);
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-variation-settings: var(--display-axes);
  font-size: clamp(2.25rem, 1.2rem + 4vw, 3rem);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.03em;
}

/* The three names cannot share one line on a phone: together they run about 15.4
   times the font size, which is more than 320px allows at any readable size. So a
   phone gets a stack of three, with no dots, and never a dot left hanging at the
   end of a line. From 40rem the size is tied to the width available, so the row
   always fits on one line (the divisor leaves about 7% spare for a fallback font). */
@media (min-width: 40rem) {
  .datestone {
    flex-direction: row;
    flex-wrap: wrap; /* only a safety net if the web font fails to load */
    align-items: baseline;
    gap: 0.2em 0;
    font-size: min(4.4rem, calc((100vw - 2 * var(--gutter)) / 16.4));
  }

  .datestone span:not(:last-child)::after {
    content: "";
    display: inline-block;
    width: 0.1em;
    height: 0.1em;
    margin-inline: 0.6em; /* equal either side, so the dot sits mid-gap */
    vertical-align: 0.24em;
    background: var(--buff-fired);
  }
}

.course--dark .datestone span:not(:last-child)::after {
  background: var(--buff);
}

/* --------------------------------------------------------- service page -- */

.service {
  padding-block: var(--course-pad);
}

/* Copy and detail stack in the wide column, the photograph holds the narrow one
   for the full height of both. Every service is built this way, so the six read
   as one repeated unit. On a phone the order is copy, photograph, detail. */
.service__grid {
  display: grid;
  gap: var(--space-lg);
}

@media (min-width: 62rem) {
  .service__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "copy media"
      "detail media";
    column-gap: var(--space-xl);
    align-items: start;
  }

  .service__copy { grid-area: copy; }
  .service__detail { grid-area: detail; }

  /* The photograph runs the full height of the copy and detail beside it, so no
     empty band is left under it. The img is positioned inside, because a
     percentage height does not resolve through the inline picture element. */
  .service__media {
    grid-area: media;
    align-self: stretch;
    position: relative;
    min-height: 22rem;
  }

  .service__media picture {
    position: absolute;
    inset: 0;
  }

  .service__grid .service__media img {
    height: 100%;
    aspect-ratio: auto;
  }
}

.service__lead {
  font-size: var(--step-1);
  max-width: 42ch;
}

.service__media {
  overflow: hidden;
  background: var(--slate-veil);
}

.service__media img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.service__detail h3 {
  margin-top: var(--space-md);
  padding-top: var(--space-xs);
  border-top: 2px solid var(--mortar);
}

.course--dark .service__detail h3 {
  border-color: color-mix(in srgb, var(--lime) 24%, transparent);
}

.service__detail h3:first-child {
  margin-top: 0;
}

/* Wide enough, the two lists sit side by side instead of stacking, so the
   detail column is no taller than the heading and lead beside it. The markup
   is a flat h3, ul, h3, ul; column flow over two rows pairs each heading with
   its list. */
@media (min-width: 75rem) {
  .service__detail {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: auto 1fr;
    grid-auto-flow: column;
    column-gap: var(--space-md);
  }

  .service__detail h3 {
    margin-top: 0;
  }

  .service__detail .detail-list {
    margin-bottom: 0;
  }

  .service__detail .draft-note {
    grid-column: 1 / -1;
    grid-row: 3;
  }
}

.detail-list {
  margin: 0 0 var(--space-sm);
  padding-left: 1.1em;
}

.detail-list li {
  margin-bottom: 0.35em;
  max-width: 52ch;
}

.detail-list li::marker {
  color: var(--buff-fired);
}

.course--dark .detail-list li::marker {
  color: var(--buff);
}

.draft-note {
  margin-top: var(--space-sm);
  font-size: var(--step--1);
}

/* ----------------------------------------------------------------- form -- */

.form-layout {
  display: grid;
  gap: var(--space-lg);
}

@media (min-width: 62rem) {
  .form-layout {
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    gap: var(--space-xl);
    align-items: start;
  }
}

.quote-form {
  display: grid;
  gap: 1.25rem;
}

/* Name / phone and email / city sit as pairs once there is room, so the form
   is four short rows shorter and reads as one plate rather than a long list. */
@media (min-width: 34rem) {
  .quote-form {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--space-md);
  }

  .quote-form > * {
    grid-column: 1 / -1;
  }

  .quote-form > .field--half {
    grid-column: auto;
  }
}

.field {
  display: grid;
  gap: 0.35em;
}

.field label {
  font-weight: 600;
}

.field .hint {
  max-width: 52ch;
  margin: 0;
  font-size: var(--step--1);
  color: var(--stone);
}

/* Borders use --stone (5.56:1), not --mortar (1.37:1), so interactive
   boundaries clear WCAG 1.4.11. Focus thickens the left edge — the same
   struck-joint move the buttons and links use. */
.field input,
.field textarea {
  width: 100%;
  padding: 0.7em 0.85em;
  font: inherit;
  color: var(--ink);
  background: var(--lime-raised);
  border: 2px solid var(--stone);
  border-radius: 0;
  transition: border-color 180ms var(--ease), background 180ms var(--ease),
    border-left-width 180ms var(--ease);
}

/* The browser default is too pale to read; --stone is 5.56:1 on the field. */
.field input::placeholder,
.field textarea::placeholder {
  color: var(--stone);
  opacity: 1;
}

.field input:hover,
.field textarea:hover {
  background: #fff;
}

.field input:focus-visible,
.field textarea:focus-visible {
  border-color: var(--ink);
  border-left-width: 6px;
  background: #fff;
}

.field textarea {
  min-height: 9rem;
  resize: vertical;
}

/* Without JavaScript the native control stays as it is. With it, the input
   becomes the invisible hit area of the drop plate below. */
.field input[type="file"] {
  padding: 0.5em;
  cursor: pointer;
}

/* ---------------------------------------------------------- upload plate -- */

/* The real <input type="file"> is stretched over the whole plate at zero
   opacity, so a click, a tap, a keyboard press and a file dropped from the
   desktop all reach the browser's own picker. What people see is the plate
   beneath it. Selected photos are listed under the plate by main.js. */
.dropzone {
  position: relative;
}

.dropzone__ui {
  display: none;
}

.js .dropzone input[type="file"] {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  opacity: 0;
  cursor: pointer;
}

.js .dropzone__ui {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: 5rem;
  padding: 0.75rem;
  background: var(--lime-raised);
  border: 2px solid var(--stone);
  transition: border-color 180ms var(--ease), background 180ms var(--ease),
    border-left-width 180ms var(--ease);
}

.dropzone:hover .dropzone__ui,
.dropzone.is-over .dropzone__ui {
  background: #fff;
}

.dropzone input:focus-visible + .dropzone__ui,
.dropzone.is-over .dropzone__ui {
  border-color: var(--ink);
  border-left-width: 6px;
}

.dropzone input:focus-visible + .dropzone__ui {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}

.dropzone.is-invalid .dropzone__ui {
  border-color: #8f2323;
  border-left-width: 6px;
}

/* A slate plate with the buff mark, the same pair the header and buttons use. */
.dropzone__plate {
  display: grid;
  flex: none;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  background: var(--slate);
  color: var(--buff);
  transition: background 180ms var(--ease), color 180ms var(--ease);
}

.dropzone:hover .dropzone__plate,
.dropzone.is-over .dropzone__plate {
  background: var(--buff);
  color: var(--ink);
}

.dropzone__plate svg {
  width: 1.75rem;
  height: 1.75rem;
}

.dropzone__text {
  display: grid;
  gap: 0.15em;
  min-width: 0;
}

.dropzone__title {
  font-weight: 700;
  font-stretch: 104%;
  line-height: 1.25;
}

.dropzone__rules {
  font-size: var(--step--1);
  line-height: 1.4;
  color: var(--stone);
}

/* Drag wording is for a pointer; a phone gets the tap wording. */
.dz-touch,
.dz-drop {
  display: none;
}

@media (hover: none), (pointer: coarse) {
  .dz-fine {
    display: none;
  }

  .dz-touch {
    display: inline;
  }
}

.dropzone.is-over .dz-fine,
.dropzone.is-over .dz-touch {
  display: none;
}

.dropzone.is-over .dz-drop {
  display: inline;
}

/* The chosen photos: one brick each, a struck joint on the left edge. */
.files {
  display: grid;
  gap: var(--space-xs);
}

.files[hidden] {
  display: none;
}

.files__list {
  display: grid;
  gap: var(--joint);
  margin: 0;
  padding: 0;
  list-style: none;
}

.file {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr) auto;
  grid-template-areas:
    "thumb name remove"
    "thumb size remove";
  column-gap: 0.85rem;
  align-items: center;
  padding: 0.5rem 0.5rem 0.5rem 0.75rem;
  background: var(--lime-raised);
  border-left: 4px solid var(--buff-fired);
}

.file__thumb {
  grid-area: thumb;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  overflow: hidden;
  background: var(--slate);
  color: var(--buff);
  font-size: 0.7rem;
  font-weight: 700;
  font-stretch: 104%;
}

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

.file__name {
  grid-area: name;
  min-width: 0;
  overflow: hidden;
  font-weight: 600;
  line-height: 1.3;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file__size {
  grid-area: size;
  font-size: var(--step--1);
  line-height: 1.3;
  color: var(--stone);
}

.file__remove {
  grid-area: remove;
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0 0.5rem;
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--buff-fired);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.22em;
  cursor: pointer;
}

.file__remove:hover {
  text-decoration-thickness: 4px;
}

.files__summary {
  margin: 0;
  max-width: none;
  font-size: var(--step--1);
  color: var(--stone);
}

.files__summary.is-over-limit {
  font-weight: 600;
  color: #8f2323;
}

.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] {
  border-color: #8f2323;
  border-left-width: 6px;
}

.field__error {
  margin: 0;
  font-size: var(--step--1);
  font-weight: 600;
  color: #8f2323;
  min-height: 1.2em;
}

.field__error:empty {
  min-height: 0;
}

.required-mark {
  color: var(--buff-fired);
}

.form-note {
  margin: 0;
  font-size: var(--step--1);
  color: var(--stone);
}

/* Hidden from people, still present for bots. Not display:none, so a bot
   filtering on that property still sees a normal-looking field. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-status {
  max-width: none;
  padding: var(--space-sm) var(--space-md);
  border-left: 6px solid var(--stone);
  background: var(--lime-raised);
  font-weight: 600;
}

.form-status[hidden] {
  display: none;
}

.form-status--success {
  border-left-color: #2f6b45;
  color: #1d4a2e;
}

.form-status--error {
  border-left-color: #8f2323;
  color: #7d2020;
}

/* Contact details are reference material, not a card. Ruled, like a plate. */
.form-aside {
  padding-top: var(--space-sm);
  border-top: 4px solid var(--ink);
}

@media (min-width: 62rem) {
  .form-aside {
    position: sticky;
    top: 6rem;
  }
}

.contact-list {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
}

.contact-list > div {
  padding-block: var(--space-sm);
  border-bottom: 2px solid var(--mortar);
}

.contact-list dt {
  margin-bottom: 0.15em;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--stone);
}

.contact-list dd {
  margin: 0;
  font-size: var(--step-1);
  font-weight: 600;
  overflow-wrap: anywhere; /* a long email address must not push the page wide */
}

.contact-list dd.contact-list__plain {
  font-size: var(--step-0);
  font-weight: 400;
}

/* --------------------------------------------------------------- footer -- */

.site-footer {
  background: var(--slate-deep);
  color: var(--lime);
  padding-block: var(--space-xl) var(--space-lg);
  border-top: 5px solid var(--buff);
}

.site-footer__grid {
  display: grid;
  gap: var(--space-lg);
}

@media (min-width: 48rem) {
  .site-footer__grid {
    grid-template-columns: 1.5fr 1fr 1fr;
    gap: var(--space-xl);
  }
}

.site-footer .site-logo {
  margin-bottom: var(--space-sm);
}

.site-footer .site-logo img {
  width: 158px;
}

.footer-descriptor {
  margin-bottom: 0.2em;
  font-weight: 600;
  font-stretch: 104%;
}

.footer-area {
  margin: 0;
  font-size: var(--step--1);
}

.site-footer h2 {
  margin-bottom: var(--space-sm);
  font-family: var(--font);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0;
  color: color-mix(in srgb, var(--lime) 66%, transparent);
}

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

.footer-list a {
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 180ms var(--ease);
}

.footer-list a:hover {
  border-bottom-color: var(--buff);
}

/* Instagram and Facebook: icons only, side by side. Each link is a 2.5rem
   square so it is an easy touch target; the glyph is Simple Icons (CC0),
   inlined so there is no request and no CSP change. */
.footer-list .social {
  display: flex;
  gap: var(--space-2xs);
  margin-left: -0.5rem; /* optical: the glyph, not its box, lines up with the text above */
}

.footer-list .social a {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
}

.social__icon {
  width: 1.5rem;
  height: 1.5rem;
}

.site-footer__bottom {
  margin-top: var(--space-xl);
  padding-top: var(--space-sm);
  border-top: 1px solid color-mix(in srgb, var(--lime) 18%, transparent);
  font-size: var(--step--1);
  color: color-mix(in srgb, var(--lime) 66%, transparent);
}

/* ------------------------------------------------------------------ 404 -- */

/* The 404 is a short course; it sets its own rhythm rather than relying on
   source order to out-rank .course. */
.course.error-page {
  padding-block: var(--space-xl);
}

/* --------------------------------------------------------------- motion -- */

/* 1. THE MASTHEAD SEQUENCE — one orchestrated moment per page load. The
   joint is struck, the title sets into it, then the supporting matter and
   the photograph. Identical on all five pages, so moving between them feels
   like one product opening a new page rather than five separate sites. */
@keyframes joint-strike {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

@keyframes set-in {
  from { opacity: 0; transform: translateY(var(--rise)); }
  to { opacity: 1; transform: translateY(0); }
}

/* The panel is uncovered left to right, the way a wall appears as the
   hoarding comes down. */
@keyframes uncover {
  from { opacity: 0; clip-path: inset(0 100% 0 0); }
  to { opacity: 1; clip-path: inset(0 0 0 0); }
}

.masthead::before {
  animation: joint-strike 700ms var(--ease) both;
}

.masthead__display {
  animation: set-in 620ms var(--ease) 160ms both;
}

.masthead__sub {
  animation: set-in 620ms var(--ease) 280ms both;
}

.masthead__lede,
.masthead__aside {
  animation: set-in 620ms var(--ease) 380ms both;
}

.masthead .actions {
  animation: set-in 620ms var(--ease) 470ms both;
}

.masthead__backdrop {
  animation: uncover 900ms var(--ease) 260ms both;
}

.plinth {
  animation: set-in 560ms var(--ease) 600ms both;
}

/* 2. THE REVEAL — everything below the masthead arrives the same way, at the
   same distance and the same speed, on every page. The hidden state is
   scoped to `.js`, which /js/init.js sets in the head, so with JavaScript
   off nothing is ever hidden.
   KEEP THIS SELECTOR LIST IN SYNC WITH `REVEAL` IN /js/main.js. */
.js .course h2,
.js .course__lede,
.js .bond__unit,
.js .spec__row,
.js .person,
.js .story__photo,
.js .work__item,
.js .datestone,
.js .service__media,
.js .service__detail,
.js .project__ba,
.js .form-layout > *,
.js .course .actions {
  opacity: 0;
  transform: translateY(var(--rise));
}

/* `.js .course h2` and `.js .course .actions` above are more specific than
   `.js .is-in`, so each needs its own revealed selector or it stays hidden. */
.js .is-in,
.js .course h2.is-in,
.js .course .actions.is-in {
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--reveal-ms) var(--ease),
    transform var(--reveal-ms) var(--ease);
  transition-delay: calc(var(--i, 0) * 70ms);
}

/* The heading's struck joint draws itself once the heading has arrived. */
.js .course h2::before {
  transform: scaleX(0);
}

.js .course h2.is-in::before {
  transform: scaleX(1);
  transition: transform 520ms var(--ease) 180ms;
}

/* The wall is laid course by course, two units at a time — the order a
   bricklayer actually works in, not one tile after another. */
.bond__unit:nth-child(1), .bond__unit:nth-child(2) { --i: 0; }
.bond__unit:nth-child(3), .bond__unit:nth-child(4) { --i: 1; }
.bond__unit:nth-child(5), .bond__unit:nth-child(6) { --i: 2; }

.spec__row:nth-child(2) { --i: 1; }
.spec__row:nth-child(3) { --i: 2; }
.spec__row:nth-child(4) { --i: 3; }

.person:nth-child(2) { --i: 1; }

.work__item:nth-child(2) { --i: 1; }
.work__item:nth-child(3) { --i: 2; }
.work__item:nth-child(4) { --i: 3; }

.form-layout > *:nth-child(2) { --i: 1; }

/* 3. REDUCED MOTION — nothing moves, and nothing is ever left hidden.
   main.js also skips the observer and marks everything revealed at once. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  .js .course h2,
  .js .course__lede,
  .js .bond__unit,
  .js .spec__row,
  .js .person,
  .js .story__photo,
  .js .work__item,
  .js .datestone,
  .js .service__media,
  .js .service__detail,
  .js .project__ba,
  .js .form-layout > *,
  .js .course .actions {
    opacity: 1;
    transform: none;
  }

  .js .course h2::before {
    transform: scaleX(1);
  }

  .work__item figure:hover .work__frame img {
    transform: none;
  }
}

@media (prefers-contrast: more) {
  :root {
    --stone: var(--ink);
    --mortar: var(--stone);
  }

  /* Type over the photograph: let the plate win outright. */
  .masthead__backdrop::after {
    background: color-mix(in srgb, var(--slate) 92%, transparent);
  }
}
