/* ==========================================================================
   v2 — shared stylesheet for every page in templates/v2
   Mobile-first. Scale up with min-width media queries.
   Append-only within v2: add rules, never remove or rename existing ones.
   --------------------------------------------------------------------------
   Requires css/fonts.css and css/tokens.css to be linked BEFORE this file.
   No font family is ever named here — typography always goes through
   var(--font-heading) or var(--font-body), both defined in tokens.css.
   --------------------------------------------------------------------------
   1.  (fonts moved to css/fonts.css)
   2.  Design tokens
   3.  Reset & base
   4.  Layout helpers
   5.  Buttons
   6.  Header & navigation (incl. dropdown)
   7.  Hero (shared)
   8.  Services
   9.  About
   10. Contact
   11. Footer
   12. Page: single-image-page
   13. Page: zoom-on-scroll
   14. Page: image-mosaic
   15. Page: full-video-hero
   16. Utilities & reduced motion
   ========================================================================== */


/* 1. Fonts ================================================================= */

/* @font-face rules live in css/fonts.css.
   Family names live in css/tokens.css (--font-heading / --font-body).
   Both must be linked before this file. */


/* 2. Design tokens ========================================================= */

:root {
  /* Colour lives in css/tokens.css — every --color-* name below is defined
     there and rebuilt from the ten-value palette in its BLOCK 1. Nothing in
     this file may name a colour directly; use a token, or compose one with
     rgb(var(--ink-rgb) / <alpha>) so it follows a rebrand. */

  /* Type — families come from css/tokens.css, sizes live here. */
  --step--1: 0.875rem;
  --step-0: 1rem;
  --step-1: 1.125rem;
  --step-2: clamp(1.35rem, 1.2rem + 0.7vw, 1.75rem);
  --step-3: clamp(1.7rem, 1.4rem + 1.4vw, 2.5rem);
  --step-4: clamp(2.1rem, 1.6rem + 2.4vw, 3.5rem);
  --step-5: clamp(2.5rem, 1.7rem + 3.8vw, 5rem);

  /* Space */
  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs: 0.75rem;
  --space-s: 1rem;
  --space-m: 1.5rem;
  --space-l: 2.25rem;
  --space-xl: 3.5rem;
  --space-2xl: 5rem;
  --space-3xl: 7rem;

  /* Shell */
  --container-max: 1200px;
  --container-pad: 1.25rem;
  --header-height: 64px;

  --radius-s: 4px;
  --radius-m: 10px;
  --radius-l: 18px;

  --shadow-s: 0 1px 2px rgb(var(--ink-rgb) / 0.06), 0 2px 8px rgb(var(--ink-rgb) / 0.05);
  --shadow-m: 0 8px 28px rgb(var(--ink-rgb) / 0.1);

  --transition: 200ms ease;
}

@media (min-width: 48em) {
  :root {
    --container-pad: 2rem;
    --header-height: 76px;
  }
}


/* 3. Reset & base ========================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchors clear the sticky header. */
  scroll-padding-top: var(--header-height);
}

body {
  margin: 0;
  background-color: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  /* A long email address, URL or hyphen-free word is otherwise a single
     unbreakable unit that widens its container and scrolls the page
     sideways. This is the cheapest protection against that on a narrow
     phone, and costs nothing on normal copy. */
  overflow-wrap: break-word;
}

/* Set by JS when the mobile menu is open. */
body.is-nav-open {
  overflow: hidden;
}

h1,
h2,
h3,
h4 {
  margin: 0 0 var(--space-s);
  font-family: var(--font-heading);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
}

h1 { font-size: var(--step-4); }
h2 { font-size: var(--step-3); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }

p {
  margin: 0 0 var(--space-s);
}

/* Browsers give <figure> a 1em/40px margin — it would break the mosaic grid. */
figure {
  margin: 0;
}

p:last-child {
  margin-bottom: 0;
}

a {
  color: var(--color-accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

a:hover {
  color: var(--color-accent-dark);
}

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

img {
  height: auto;
}

/* <picture> is only a wrapper — it has to stretch, or the object-fit
   rules on the inner <img> have no height to resolve against. */
.hero-media picture,
.zoom-frame picture,
.mosaic-item picture {
  width: 100%;
  height: 100%;
}

ul,
ol {
  margin: 0 0 var(--space-s);
  padding-left: 1.2em;
}

hr {
  height: 1px;
  margin: var(--space-l) 0;
  border: 0;
  background-color: var(--color-line);
}

:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: var(--radius-s);
}

/* The accent ring is 6:1 on the page background but collapses to ~1.1:1
   against the scrim over a bright hero — an invisible focus indicator for
   keyboard users. Inside a transparent header the ring follows the same
   light/dark logic as the text. */
.site-header--overlay :focus-visible {
  outline-color: var(--color-inverse);
}

/* ...but the dropdown panel and the open mobile panel both have a solid
   light background of their own, where a white ring would disappear again. */
.site-header--overlay .submenu :focus-visible,
.site-header--overlay.site-header--light :focus-visible,
.site-header--overlay.is-stuck :focus-visible,
.site-header--overlay.is-nav-open :focus-visible {
  outline-color: var(--color-accent);
}


/* 4. Layout helpers ======================================================== */

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

.container--narrow {
  max-width: 760px;
}

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

@media (min-width: 48em) {
  .section {
    padding-block: var(--space-2xl);
  }
}

@media (min-width: 64em) {
  .section {
    padding-block: var(--space-3xl);
  }
}

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

.section--tight {
  padding-block: var(--space-l);
}

.section-head {
  max-width: 46rem;
  margin-bottom: var(--space-l);
}

.section-head--center {
  margin-inline: auto;
  text-align: center;
}

.eyebrow {
  display: block;
  margin-bottom: var(--space-2xs);
  color: var(--color-accent);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.lead {
  color: var(--color-ink-soft);
  font-size: var(--step-1);
}


/* 5. Buttons =============================================================== */

.btn {
  display: inline-block;
  padding: 0.8rem 1.6rem;
  border: 1px solid transparent;
  border-radius: 999px;
  background-color: var(--color-accent);
  color: var(--color-inverse);
  font-family: inherit;
  font-size: var(--step-0);
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--transition), color var(--transition),
    border-color var(--transition);
}

.btn:hover {
  background-color: var(--color-accent-dark);
  color: var(--color-inverse);
}

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

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

.btn--on-dark {
  border-color: var(--color-border-inverse);
  background-color: transparent;
  color: var(--color-inverse);
}

.btn--on-dark:hover {
  background-color: var(--color-inverse);
  color: var(--color-ink);
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  margin-top: var(--space-m);
}

.btn-row--center {
  justify-content: center;
}


/* 6. Header & navigation =================================================== */

.skip-link {
  position: absolute;
  top: -100px;
  left: var(--space-s);
  z-index: 200;
  padding: var(--space-2xs) var(--space-s);
  border-radius: var(--radius-s);
  background-color: var(--color-accent);
  color: var(--color-inverse);
  text-decoration: none;
}

.skip-link:focus {
  top: var(--space-2xs);
  color: var(--color-inverse);
}

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

/* Transparent header sitting over a full-bleed hero.
   ------------------------------------------------------------------
   A transparent header cannot rely on the photo behind it being dark:
   swap in a bright image and white nav text disappears. Three layers
   keep it readable on ANY hero, including a white one:

   1. A scrim (::before) — a dark gradient owned by the header itself,
      not by the hero overlay, so contrast survives even if the hero
      image or its overlay is changed.
   2. A text-shadow on the header text, for busy or blown-out photos.
   3. .site-header--light — the modifier for heroes that are genuinely
      light. It flips the scrim and the text to ink instead of white.

   The selected page is never marked by colour alone (see the
   .nav-link[aria-current] rules further down) because no single colour
   is readable against every photograph. */
.site-header--overlay {
  position: fixed;
  right: 0;
  left: 0;
  background-color: transparent;
  border-bottom-color: transparent;
  transition: background-color var(--transition), border-color var(--transition);
}

/* Stops chosen against the worst case — a pure white hero photo. The nav
   text sits at roughly 35-45% of this box, where the scrim holds ~0.66
   alpha: white text on that measures ~6.1:1, clear of the 4.5:1 AA
   threshold for 14px text. Softening the middle stop to 0.42 drops it to
   3.7:1, so keep the fade in the bottom third. */
.site-header--overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to bottom,
    rgb(var(--ink-rgb) / var(--scrim-header-top)) 0%,
    rgb(var(--ink-rgb) / var(--scrim-header-fade)) 62%,
    rgb(var(--ink-rgb) / 0) 100%
  );
  /* Taller than the header so the fade ends softly over the image. */
  bottom: -40%;
  pointer-events: none;
  transition: opacity var(--transition);
}

.site-header--overlay .nav-link,
.site-header--overlay .brand,
.site-header--overlay .nav-toggle,
.site-header--overlay .nav-toggle-submenu {
  color: var(--color-inverse);
  text-shadow: 0 1px 3px rgb(var(--ink-rgb) / 0.55);
}

.site-header--overlay .brand-mark {
  border-color: var(--color-on-dark);
}

.site-header--overlay .nav-toggle-bar {
  background-color: var(--color-inverse);
}

/* Light-image variant: put this on the header when the hero photo is
   bright (a white studio shot, snow, pale sky). Same structure, inverted. */
.site-header--overlay.site-header--light::before {
  background: linear-gradient(
    to bottom,
    rgb(var(--inverse-rgb) / var(--scrim-light-top)) 0%,
    rgb(var(--inverse-rgb) / var(--scrim-light-fade)) 62%,
    rgb(var(--inverse-rgb) / 0) 100%
  );
}

.site-header--overlay.site-header--light .nav-link,
.site-header--overlay.site-header--light .brand,
.site-header--overlay.site-header--light .nav-toggle,
.site-header--overlay.site-header--light .nav-toggle-submenu {
  color: var(--color-ink);
  text-shadow: 0 1px 3px rgb(var(--inverse-rgb) / 0.75);
}

.site-header--overlay.site-header--light .brand-mark {
  border-color: var(--color-accent);
}

.site-header--overlay.site-header--light .nav-toggle-bar {
  background-color: var(--color-ink);
}

/* Once the header goes solid the scrim is redundant. */
.site-header--overlay.is-stuck::before,
.site-header--overlay.is-nav-open::before {
  opacity: 0;
}

/* Added by JS once the page scrolls past the hero fold. */
.site-header--overlay.is-stuck,
.site-header--overlay.is-nav-open {
  background-color: var(--color-surface);
  border-bottom-color: var(--color-line);
}

.site-header--overlay.is-stuck .nav-link,
.site-header--overlay.is-stuck .brand,
.site-header--overlay.is-stuck .nav-toggle,
.site-header--overlay.is-stuck .nav-toggle-submenu,
.site-header--overlay.is-nav-open .nav-link,
.site-header--overlay.is-nav-open .brand,
.site-header--overlay.is-nav-open .nav-toggle,
.site-header--overlay.is-nav-open .nav-toggle-submenu {
  color: var(--color-ink);
  text-shadow: none;
}

.site-header--overlay.is-stuck .brand-mark,
.site-header--overlay.is-nav-open .brand-mark {
  border-color: var(--color-accent);
}

.site-header--overlay.is-stuck .nav-toggle-bar,
.site-header--overlay.is-nav-open .nav-toggle-bar {
  background-color: var(--color-ink);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-s);
  min-height: var(--header-height);
}

/* min-width:0 is load-bearing. A flex item defaults to min-width:auto, so it
   refuses to shrink below its own min-content width — and .brand-name is
   nowrap, making that min-content the full untruncated business name. With a
   long name ("Constantinos Kalavas LLC" is 250px at 390px viewport) the brand
   wins the space fight and pushes the Menu button off the right edge, where
   body{overflow-x:hidden} silently clips it. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-width: 0;
  color: var(--color-ink);
  font-family: var(--font-heading);
  font-size: var(--brand-wordmark-size);
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.brand-mark {
  display: grid;
  place-items: center;
  width: var(--brand-mark-size);
  height: var(--brand-mark-size);
  flex: 0 0 auto;
  border: 1.5px solid var(--color-accent);
  border-radius: 50%;
  /* Scales with the mark so the initials never crowd the circle. */
  font-size: calc(var(--brand-mark-size) * 0.38);
  letter-spacing: 0;
}

/* Image logo. Use INSTEAD of .brand-mark:
     <a class="brand" href="...">
       <img class="brand-logo" src="images/logo.svg" alt="[BUSINESS NAME]"
            width="240" height="48">
       <span class="brand-name">[BUSINESS NAME]</span>   <- optional
     </a>
   Capped by height, with width left to follow, so the aspect ratio holds and
   any source size works: a small logo shows at its natural size, an oversized
   one is scaled down instead of blowing out the header. Always keep the
   width/height attributes on the tag — they reserve the space and stop the
   header jumping while the image loads. */
.brand-logo {
  display: block;
  /* A DEFINITE height, not a max. An SVG that carries only a viewBox has an
     aspect ratio but no intrinsic width or height, and this rule overrides
     the width/height attributes on the tag (they are only presentation
     hints). Leave both dimensions on auto and there is nothing left to size
     from: the flex base size resolves to 0 and the logo vanishes. Give it
     one definite dimension and the ratio supplies the other. */
  height: var(--brand-logo-height);
  width: auto;
  /* Both caps must be independent of .brand, or the logo collapses.
     The base reset sets img{max-width:100%}, and that percentage resolves
     against .brand — whose own width comes from its content, i.e. this
     image. Circular, so it resolves to 0x0 whenever the logo is the only
     child of .brand (a logo with no wordmark), which looks fine in a design
     and disappears in the browser. Letting flex shrink it instead
     (min-width:0) collapses it the same way, from the other direction.
     So: cap the height against the header, cap the width against the
     VIEWPORT, and take the image out of the flex-shrink fight entirely.
     Both caps are maxima, so the browser keeps the aspect ratio and honours
     whichever one binds first. --brand-logo-max-width leaves room for the
     menu button, which is the one thing that must never yield. */
  max-width: var(--brand-logo-max-width);
  /* When the width cap binds on a very narrow screen, contain letterboxes
     the artwork inside the box instead of squashing it. */
  object-fit: contain;
  object-position: left center;
  flex: 0 0 auto;
}

/* Truncate rather than overflow: a name too long for the bar loses its tail
   to an ellipsis instead of shoving the menu button out of the viewport. */
.brand-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Optional: drop the wordmark on the narrowest screens and let the logo
   speak for itself. Useful when the business name is long and the logo
   already contains it. The name stays in the accessibility tree. */
@media (max-width: 26.25em) {
  .brand-name--compact {
    position: absolute !important;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

/* Mobile menu button */
/* The only way into the navigation on a phone — it must never be the thing
   that yields when the header runs out of room. */
.nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  flex-shrink: 0;
  padding: var(--space-2xs);
  border: 0;
  background: none;
  color: var(--color-ink);
  font: inherit;
  font-size: var(--step--1);
  font-weight: 600;
  cursor: pointer;
}

.nav-toggle-icon {
  display: grid;
  gap: 4px;
  width: 22px;
}

.nav-toggle-bar {
  display: block;
  height: 2px;
  background-color: var(--color-ink);
  transition: transform var(--transition), opacity var(--transition);
}

.nav-toggle[aria-expanded="true"] .nav-toggle-bar:nth-child(1) {
  transform: translateY(6px) rotate(45deg);
}

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

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

/* Mobile: panel below the header, toggled by JS. */
.primary-nav {
  position: absolute;
  top: 100%;
  right: 0;
  left: 0;
  display: none;
  max-height: calc(100vh - var(--header-height));
  overflow-y: auto;
  padding: var(--space-xs) var(--container-pad) var(--space-m);
  background-color: var(--color-surface);
  border-bottom: 1px solid var(--color-line);
  box-shadow: var(--shadow-m);
}

.primary-nav.is-open {
  display: block;
}

.nav-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.nav-item {
  border-bottom: 1px solid var(--color-line);
}

.nav-item:last-child {
  border-bottom: 0;
}

.nav-link {
  display: block;
  padding: var(--space-xs) 0;
  color: var(--color-ink);
  font-size: var(--step-0);
  font-weight: 500;
  text-decoration: none;
}

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

/* Current page.
   ------------------------------------------------------------------
   Never marked by colour alone: no single colour survives an arbitrary
   photo behind it, and colour alone is also invisible to a colour-blind
   visitor. On mobile the panel is solid, so weight + colour is enough.
   The desktop bar — the one that sits over the hero image — adds a rule
   and a chip; those rules are in the desktop block further down. */
.nav-link[aria-current="page"] {
  position: relative;
  color: var(--color-accent);
  font-weight: 700;
}

.site-header--overlay .nav-link[aria-current="page"],
.site-header--overlay .nav-toggle-submenu--current {
  color: var(--color-inverse);
}

.site-header--overlay.site-header--light .nav-link[aria-current="page"],
.site-header--overlay.site-header--light .nav-toggle-submenu--current {
  color: var(--color-accent-dark);
}

.site-header--overlay.is-stuck .nav-link[aria-current="page"],
.site-header--overlay.is-stuck .nav-toggle-submenu--current,
.site-header--overlay.is-nav-open .nav-link[aria-current="page"],
.site-header--overlay.is-nav-open .nav-toggle-submenu--current {
  color: var(--color-accent);
}

/* Dropdown trigger + panel */
.nav-item--has-menu {
  position: relative;
}

.nav-toggle-submenu {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2xs);
  width: 100%;
  padding: var(--space-xs) 0;
  border: 0;
  background: none;
  color: var(--color-ink);
  font-family: inherit;
  font-size: var(--step-0);
  font-weight: 500;
  line-height: 1.65;
  text-align: left;
  cursor: pointer;
}

.nav-toggle-submenu:hover {
  color: var(--color-accent);
}

/* Add this to the Services trigger on any page whose only "you are here"
   marker would otherwise be hidden inside the collapsed dropdown. Leave it
   off when a top-level link already points at the current page, so the bar
   never shows two markers at once. Kept as an explicit class rather than a
   :has() selector — see the note in the nav section of README-ish comments. */
.nav-toggle-submenu--current {
  position: relative;
  color: var(--color-accent);
  font-weight: 700;
}

.nav-caret {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  transition: transform var(--transition);
}

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

.submenu {
  display: none;
  margin: 0 0 var(--space-xs);
  padding: 0 0 0 var(--space-s);
  border-left: 2px solid var(--color-accent-soft);
  list-style: none;
}

.submenu.is-open {
  display: block;
}

.submenu-link {
  display: block;
  padding: var(--space-2xs) 0;
  color: var(--color-ink-soft);
  font-size: var(--step--1);
  text-decoration: none;
}

.submenu-link:hover {
  color: var(--color-accent);
}

/* Current page inside the dropdown. Without this the three pages that live
   under Services show no "you are here" marker at all — the only element
   carrying aria-current is hidden inside a collapsed menu. */
.submenu-link[aria-current="page"] {
  color: var(--color-accent);
  font-weight: 700;
}

/* Desktop navigation */
@media (min-width: 62em) {
  .nav-toggle {
    display: none;
  }

  .primary-nav {
    position: static;
    display: block;
    max-height: none;
    overflow: visible;
    padding: 0;
    background-color: transparent;
    border-bottom: 0;
    box-shadow: none;
  }

  .nav-list {
    display: flex;
    align-items: center;
    gap: var(--space-m);
  }

  .nav-item {
    border-bottom: 0;
  }

  .nav-link,
  .nav-toggle-submenu {
    padding: var(--space-2xs) 0;
    font-size: var(--step--1);
    letter-spacing: 0.02em;
  }

  .nav-toggle-submenu {
    width: auto;
  }

  .submenu {
    position: absolute;
    top: calc(100% + 10px);
    left: -1rem;
    z-index: 10;
    min-width: 230px;
    margin: 0;
    padding: var(--space-2xs);
    border: 1px solid var(--color-line);
    border-left: 1px solid var(--color-line);
    border-radius: var(--radius-m);
    background-color: var(--color-surface);
    box-shadow: var(--shadow-m);
  }

  .submenu-link {
    padding: var(--space-2xs) var(--space-xs);
    border-radius: var(--radius-s);
    color: var(--color-ink);
  }

  .submenu-link:hover {
    background-color: var(--color-accent-soft);
    color: var(--color-accent-dark);
  }

  /* Current page in the desktop bar — a 2px rule under the label. */
  .nav-link[aria-current="page"]::after,
  .nav-toggle-submenu--current::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: 2px;
    left: 0;
    height: 2px;
    border-radius: 2px;
    background-color: currentColor;
  }

  /* Over a photo the label also gets a chip, so the marker holds up on a
     white, dark or busy image. Sits behind the text: the header is the
     stacking context, so the negative z-index cannot escape it. */
  .site-header--overlay .nav-link[aria-current="page"]::before,
  .site-header--overlay .nav-toggle-submenu--current::before {
    content: "";
    position: absolute;
    inset: -2px -10px;
    z-index: -1;
    border-radius: var(--radius-s);
    background-color: rgb(var(--inverse-rgb) / 0.18);
  }

  .site-header--overlay.site-header--light .nav-link[aria-current="page"]::before,
  .site-header--overlay.site-header--light .nav-toggle-submenu--current::before {
    background-color: rgb(var(--ink-rgb) / 0.1);
  }

  /* Solid header: real background behind the text, so drop the chip. */
  .site-header--overlay.is-stuck .nav-link[aria-current="page"]::before,
  .site-header--overlay.is-stuck .nav-toggle-submenu--current::before,
  .site-header--overlay.is-nav-open .nav-link[aria-current="page"]::before,
  .site-header--overlay.is-nav-open .nav-toggle-submenu--current::before {
    background-color: transparent;
  }

  /* Hover on the transparent bar needs the same treatment — accent teal
     on a photograph is not reliably legible. */
  .site-header--overlay .nav-link:hover,
  .site-header--overlay .nav-toggle-submenu:hover {
    color: var(--color-inverse);
    text-decoration: underline;
    text-underline-offset: 6px;
    text-decoration-thickness: 2px;
  }

  .site-header--overlay.site-header--light .nav-link:hover,
  .site-header--overlay.site-header--light .nav-toggle-submenu:hover {
    color: var(--color-accent-dark);
  }

  .site-header--overlay.is-stuck .nav-link:hover,
  .site-header--overlay.is-stuck .nav-toggle-submenu:hover,
  .site-header--overlay.is-nav-open .nav-link:hover,
  .site-header--overlay.is-nav-open .nav-toggle-submenu:hover {
    color: var(--color-accent);
    text-decoration: none;
  }
}


/* 7. Hero (shared) ========================================================= */

.hero {
  position: relative;
  display: flex;
  align-items: center;
  overflow: hidden;
  background-color: var(--color-ink);
  color: var(--color-inverse);
}

.hero--full {
  min-height: 100vh;
}

/* Progressive enhancement: dvh avoids the mobile URL-bar jump where supported. */
@supports (height: 100dvh) {
  .hero--full {
    min-height: 100dvh;
  }
}

.hero--compact {
  min-height: 58vh;
}

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

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

.hero-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to bottom,
    var(--color-overlay-strong) 0%,
    var(--color-overlay) 45%,
    var(--color-overlay-strong) 100%
  );
}

.hero-content {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-block: calc(var(--header-height) + var(--space-2xl)) var(--space-2xl);
  text-align: center;
}

.hero-content--left {
  text-align: left;
}

.hero-title {
  max-width: 18ch;
  margin-inline: auto;
  font-size: var(--step-5);
  color: var(--color-inverse);
  text-wrap: balance;
}

.hero-content--left .hero-title {
  margin-inline: 0;
}

.hero-text {
  max-width: 48ch;
  margin-inline: auto;
  color: var(--color-on-media);
  font-size: var(--step-1);
}

.hero-content--left .hero-text {
  margin-inline: 0;
}

.hero .btn-row {
  justify-content: center;
}

.hero-content--left .btn-row {
  justify-content: flex-start;
}

.hero-eyebrow {
  color: var(--color-on-media-soft);
}

.scroll-cue {
  position: absolute;
  bottom: var(--space-m);
  left: 50%;
  z-index: 2;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3xs);
  transform: translateX(-50%);
  color: var(--color-on-media);
  font-size: var(--step--1);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
}

.scroll-cue:hover {
  color: var(--color-inverse);
}

.scroll-cue svg {
  animation: nudge 2s ease-in-out infinite;
}

@keyframes nudge {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(5px); }
}


/* 8. Services ============================================================== */

.card-grid {
  display: grid;
  gap: var(--space-m);
}

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

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

.card {
  padding: var(--space-m);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-l);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-s);
}

.card-icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-bottom: var(--space-s);
  border-radius: var(--radius-m);
  background-color: var(--color-accent-soft);
  color: var(--color-accent-dark);
}

.card h3 {
  margin-bottom: var(--space-2xs);
}

.card p {
  color: var(--color-ink-soft);
}


/* 9. About ================================================================= */

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

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

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

.split-media img {
  width: 100%;
  border-radius: var(--radius-l);
}

.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-l);
  margin-top: var(--space-m);
  padding-top: var(--space-m);
  border-top: 1px solid var(--color-line);
}

.stat-value {
  display: block;
  font-family: var(--font-heading);
  font-size: var(--step-3);
  line-height: 1.1;
}

.stat-label {
  color: var(--color-ink-soft);
  font-size: var(--step--1);
}


/* 10. Contact ============================================================== */

.contact-grid {
  display: grid;
  gap: var(--space-m);
}

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

.contact-item {
  /* Grid items default to min-width:auto, so a wide child can push the card
     past its track. Allow it to shrink to the column it was given. */
  min-width: 0;
  padding: var(--space-m);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-l);
  background-color: var(--color-surface);
}

.contact-item h3 {
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
}

.contact-item p,
.contact-item a {
  font-size: var(--step-1);
}

.contact-item a {
  text-decoration: none;
}

.contact-item a:hover {
  text-decoration: underline;
}

.hours-list {
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--color-ink-soft);
}

.hours-list li {
  /* At very small widths with large text the day and the time no longer fit
     on one line — let them stack rather than widen the card. */
  flex-wrap: wrap;
  display: flex;
  justify-content: space-between;
  gap: var(--space-s);
  padding: var(--space-3xs) 0;
}


/* 11. Footer =============================================================== */

.site-footer {
  padding-block: var(--space-xl) var(--space-m);
  background-color: var(--color-ink);
  color: rgb(var(--inverse-rgb) / 0.75);
}

.footer-grid {
  display: grid;
  gap: var(--space-l);
}

@media (min-width: 48em) {
  .footer-grid {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr);
  }
}

.site-footer h2,
.site-footer h3 {
  color: var(--color-inverse);
}

.site-footer h3 {
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.site-footer a {
  color: var(--color-on-dark);
  text-decoration: none;
}

.site-footer a:hover {
  color: var(--color-inverse);
  text-decoration: underline;
}

.footer-brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-bottom: var(--space-xs);
  color: var(--color-inverse);
  font-family: var(--font-heading);
  font-size: var(--step-2);
}

.footer-brand .brand-mark {
  border-color: var(--color-border-inverse);
}

.footer-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer-list li {
  padding: var(--space-3xs) 0;
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-m);
  justify-content: space-between;
  margin-top: var(--space-l);
  padding-top: var(--space-m);
  border-top: 1px solid var(--color-hairline-inverse);
  font-size: var(--step--1);
}


/* 12. Page: single-image-page ============================================== */

.hero--single-image .hero-media img {
  /* Keep faces/subject near the upper third on tall phone screens. */
  object-position: 50% 40%;
}

.figure-band {
  margin: 0;
}

.figure-band img {
  width: 100%;
  border-radius: var(--radius-l);
}

.figure-band figcaption {
  margin-top: var(--space-2xs);
  color: var(--color-ink-soft);
  font-size: var(--step--1);
}


/* 13. Page: zoom-on-scroll ================================================= */

/* The frame is the scroll track; the media sticks while the page moves past. */
.zoom-stage {
  position: relative;
  height: 220vh;
  background-color: var(--color-ink);
}

.zoom-sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
}

@supports (height: 100dvh) {
  .zoom-sticky {
    height: 100dvh;
  }
}

.zoom-frame {
  position: absolute;
  inset: 0;
  /* --zoom is written by js/script.js on scroll (1 → 1.35). */
  transform: scale(var(--zoom, 1));
  transform-origin: center center;
  will-change: transform;
}

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

.zoom-caption {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-l) var(--container-pad);
  background-color: var(--color-overlay);
  color: var(--color-inverse);
  text-align: center;
  /* --zoom-fade is written by js/script.js (1 → 0). */
  opacity: var(--zoom-fade, 1);
}

.zoom-caption h1,
.zoom-caption h2 {
  max-width: 20ch;
  margin-inline: auto;
  color: var(--color-inverse);
  font-size: var(--step-4);
  text-wrap: balance;
}

.zoom-caption p {
  max-width: 44ch;
  margin-inline: auto;
  color: var(--color-on-media);
}

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 600ms ease, transform 600ms ease;
}

.reveal.is-visible {
  opacity: 1;
  transform: none;
}


/* 14. Page: image-mosaic =================================================== */

.mosaic {
  display: grid;
  gap: var(--space-2xs);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: 42vw;
}

@media (min-width: 48em) {
  .mosaic {
    gap: var(--space-s);
    grid-template-columns: repeat(4, minmax(0, 1fr));
    grid-auto-rows: 16vw;
  }
}

@media (min-width: 90em) {
  .mosaic {
    grid-auto-rows: 190px;
  }
}

.mosaic-item {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-m);
  background-color: var(--color-surface-alt);
}

.mosaic-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 500ms ease;
}

.mosaic-item:hover img,
.mosaic-item:focus-within img {
  transform: scale(1.05);
}

.mosaic-caption {
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--space-m) var(--space-s) var(--space-s);
  /* The caption text band sits roughly 25-60% up this box, so the gradient
     has to still be dense there — a plain 0.8-to-transparent ramp thins out
     to ~0.3 behind the top of the text and fails on a bright photo. */
  background: linear-gradient(
    to top,
    rgb(var(--ink-rgb) / var(--scrim-caption-base)) 0%,
    rgb(var(--ink-rgb) / var(--scrim-caption-mid)) 70%,
    rgb(var(--ink-rgb) / 0) 100%
  );
  color: var(--color-inverse);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* Spans — chosen so the grid fills completely at every breakpoint. */
.mosaic-item--wide {
  grid-column: span 2;
}

.mosaic-item--tall {
  grid-row: span 2;
}

/* Two columns wide on phones, a 2x2 block from tablet up. */
.mosaic-item--feature {
  grid-column: span 2;
}

/* Full-width closing band. */
.mosaic-item--band {
  grid-column: 1 / -1;
}

@media (min-width: 48em) {
  .mosaic-item--feature {
    grid-row: span 2;
  }
}


/* 15. Page: full-video-hero ================================================ */

.hero--video .hero-media video {
  /* Poster shows first and stays if the video never loads. */
  background-color: var(--color-ink);
}

.video-controls {
  position: absolute;
  right: var(--container-pad);
  bottom: var(--space-m);
  z-index: 3;
}

.video-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding: 0.5rem 1rem;
  border: 1px solid var(--color-border-inverse);
  border-radius: 999px;
  /* Sits directly on the video, so it needs its own opaque-enough chip —
     the frame behind it can be any colour, including near-white. */
  background-color: rgb(var(--ink-rgb) / var(--scrim));
  color: var(--color-inverse);
  font: inherit;
  font-size: var(--step--1);
  cursor: pointer;
  transition: background-color var(--transition);
}

.video-toggle:hover {
  background-color: rgb(var(--ink-rgb) / 0.85);
}

.signature-strip {
  display: grid;
  gap: var(--space-s);
  padding-block: var(--space-l);
  border-bottom: 1px solid var(--color-line);
  text-align: center;
}

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

.signature-strip p {
  color: var(--color-ink-soft);
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}


/* 16. Utilities & reduced motion =========================================== */

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

.text-center {
  text-align: center;
}

.no-wrap {
  white-space: nowrap;
}

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

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

  .zoom-frame {
    transform: none;
  }

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


/* 17. Page: custom-layout — the placement canvas =========================== */

/* Free placement of text and images without absolute positioning.

   Why a grid and not position: absolute?
   Absolute positioning gives freedom by taking blocks out of flow, so a
   text block that grows spills over whatever is beneath it, the container
   needs a hard-coded height, and DOM order gets welded to visual order.
   On a grid the blocks keep participating in layout: they can sit anywhere,
   overlap on purpose, and stack on a phone in one line of CSS — and a row
   simply grows if its content needs more space.

   HOW TO PLACE A BLOCK
   Put .canvas on the container and .canvas-item on each child, then set
   these in the style attribute. They only apply from 48em up; below that
   every item is full width in source order.

     --col       column the block starts on          1-12   (default 1)
     --span      how many columns it covers          1-12   (default 12)
     --row       row the block starts on             number (default auto)
     --row-span  how many rows it covers             number (default 1)
     --layer     stacking order when blocks overlap  number (default 1)
     --align     vertical position in its cell       start | center | end
     --justify   horizontal position in its cell     start | center | end
     --nudge-x   optical offset, e.g. -1rem          any length
     --nudge-y   optical offset, e.g. 2rem           any length

   Two blocks overlap when their column and row ranges intersect — that is
   intentional. Give the one that should sit on top a higher --layer, and
   keep text on the higher layer so it stays readable. */


/* 1. The canvas =========================================================== */

.canvas {
  --canvas-cols: 12;
  --canvas-row: clamp(44px, 5.5vw, 88px);

  display: grid;
  grid-template-columns: repeat(var(--canvas-cols), minmax(0, 1fr));
  gap: var(--space-s);
  /* minmax keeps the rhythm of the row unit but lets a row grow rather
     than clip when the content inside it is taller. */
  grid-auto-rows: minmax(var(--canvas-row), auto);
}

/* Tighter gutters on a phone, where every pixel of width counts. */
@media (max-width: 47.99em) {
  .canvas {
    gap: var(--space-m);
  }
}

.canvas--flush {
  gap: 0;
}

.canvas--roomy {
  gap: var(--space-m);
}


/* 2. Canvas items ========================================================= */

/* Mobile-first: one column, source order, no surprises. */
.canvas-item {
  position: relative;
  grid-column: 1 / -1;
  /* Grid items default to min-width:auto, which lets long words and wide
     media push a track past its share. */
  min-width: 0;
}

@media (min-width: 48em) {
  .canvas-item {
    grid-column: var(--col, 1) / span var(--span, 12);
    grid-row: var(--row, auto) / span var(--row-span, 1);
    align-self: var(--align, stretch);
    justify-self: var(--justify, stretch);
    /* Grid items honour z-index without needing position: relative. */
    z-index: var(--layer, 1);
  }

  /* Only pay for a transform when a nudge is actually set. */
  .canvas-item[style*="--nudge"] {
    transform: translate(var(--nudge-x, 0), var(--nudge-y, 0));
  }
}


/* 3. Media blocks ========================================================= */

.canvas-media {
  overflow: hidden;
  border-radius: var(--radius-l);
  background-color: var(--color-surface-alt);
}

/* <picture> is only a wrapper — without this the inner img has no height
   to resolve object-fit against. Same trap as the hero and mosaic blocks. */
.canvas-media picture {
  display: block;
  width: 100%;
  height: 100%;
}

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

/* On a phone there are no row spans to give an image its height, so give
   it a shape of its own instead of letting it run full bleed. */
@media (max-width: 47.99em) {
  .canvas-media img {
    aspect-ratio: 4 / 3;
    height: auto;
  }

  .canvas-media--tall img {
    aspect-ratio: 3 / 4;
  }

  .canvas-media--wide img {
    aspect-ratio: 16 / 9;
  }
}

.canvas-media--square {
  border-radius: var(--radius-m);
}

/* Let a block run to the edge of the viewport, past the container padding. */
.canvas-item--bleed {
  margin-inline: calc(var(--container-pad) * -1);
}

@media (min-width: 48em) {
  .canvas-item--bleed .canvas-media {
    border-radius: 0;
  }
}


/* 4. Text blocks ========================================================== */

.canvas-text {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* A solid card, for text that sits over an image. Opaque on purpose:
   translucent panels stop being readable the moment the photo behind
   them changes. */
.canvas-panel {
  padding: var(--space-m);
  border-radius: var(--radius-l);
  background-color: var(--color-surface);
  box-shadow: var(--shadow-m);
}

@media (min-width: 64em) {
  .canvas-panel {
    padding: var(--space-l);
  }
}

.canvas-panel--ink {
  background-color: var(--color-ink);
  color: var(--color-on-media);
}

.canvas-panel--ink h2,
.canvas-panel--ink h3 {
  color: var(--color-inverse);
}

.canvas-panel--ink .eyebrow {
  color: var(--color-accent-soft);
}

.canvas-panel--accent {
  background-color: var(--color-accent);
  color: var(--color-inverse);
}

.canvas-panel--accent h2,
.canvas-panel--accent h3 {
  color: var(--color-inverse);
}

.canvas-panel--accent .eyebrow {
  color: var(--color-on-media-soft);
}

.canvas-panel p:last-child {
  margin-bottom: 0;
}

/* Oversized editorial number/label for art-directed blocks. */
.canvas-figure {
  display: block;
  font-family: var(--font-heading);
  font-size: clamp(3rem, 2rem + 5vw, 6rem);
  font-weight: 600;
  line-height: 0.9;
  color: var(--color-accent);
}

.canvas-label {
  display: block;
  margin-top: var(--space-2xs);
  color: var(--color-ink-soft);
  font-size: var(--step--1);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}


/* 5. Caption sitting on top of a media block ============================== */

/* Anchored inside .canvas-media, which is the positioned ancestor. */
.canvas-caption {
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--space-l) var(--space-m) var(--space-m);
  /* Dense through the text band, fading only above it — a thin ramp fails
     as soon as someone drops in a bright photograph. */
  background: linear-gradient(
    to top,
    rgb(var(--ink-rgb) / var(--scrim-caption-base)) 0%,
    rgb(var(--ink-rgb) / var(--scrim-caption-mid)) 70%,
    rgb(var(--ink-rgb) / 0) 100%
  );
  color: var(--color-inverse);
}

.canvas-caption h3 {
  margin-bottom: var(--space-3xs);
  color: var(--color-inverse);
}

.canvas-caption p {
  margin-bottom: 0;
  color: var(--color-on-media);
  font-size: var(--step--1);
}


/* 6. Guides — a development aid, off by default =========================== */

/* Add .canvas--guides to a canvas while positioning blocks to see the
   column tracks. Remove it before the site goes live. The stripes are
   calculated for the default gap, so they drift on .canvas--roomy or
   .canvas--flush — they are a rough aid, not a measurement. */
.canvas--guides {
  position: relative;
}

.canvas--guides::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    to right,
    rgb(var(--accent-rgb) / 0.09) 0,
    rgb(var(--accent-rgb) / 0.09) calc((100% - 11 * var(--space-s)) / 12),
    transparent calc((100% - 11 * var(--space-s)) / 12),
    transparent calc((100% - 11 * var(--space-s)) / 12 + var(--space-s))
  );
}
