/* ==========================================================================
   ProPaint Corp — stylesheet
   Reference implementation. Other pages inherit this file unchanged.

   COLOUR RULE: every literal colour value lives in the :root block below.
   A hex or rgb()/rgba() literal anywhere outside :root is a build failure.
   See decisions.md §1.
   ========================================================================== */

/* --------------------------------------------------------------------------
   SELF-HOSTED FONTS — active. Latin subset, woff2 only, 88KB for all four.
   No third-party connection, nothing render-blocking, no visitor IPs to Google.
   font-display:swap means text paints immediately in the fallback and reflows
   once Barlow arrives — never invisible.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Barlow Condensed';
  src: url('assets/fonts/barlow-condensed-latin-700-normal.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Barlow';
  src: url('assets/fonts/barlow-latin-400-normal.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Barlow';
  src: url('assets/fonts/barlow-latin-600-normal.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Barlow';
  src: url('assets/fonts/barlow-latin-700-normal.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ========== 1. RAW PALETTE — the only literal colours in this file ========== */
  --c-teal:   #0E3B43;
  --c-plum:   #592941;
  --c-orange: #CF5C36;
  --c-amber:  #FDB833;
  --c-ink:    #351431;
  --c-paper:  #F5F1EC;
  --c-white:  #FFFFFF;

  /* Opacity variants. Declared here so no rgba() is ever written inline. */
  --c-ink-70:     rgba(53, 20, 49, 0.70);
  --c-ink-12:     rgba(53, 20, 49, 0.12);
  --c-ink-14:     rgba(53, 20, 49, 0.14);
  --c-ink-20:     rgba(53, 20, 49, 0.20);
  --c-white-70:   rgba(255, 255, 255, 0.70);
  --c-white-12:   rgba(255, 255, 255, 0.12);
  --c-scrim-45:   rgba(53, 20, 49, 0.45);
  --c-scrim-08:   rgba(53, 20, 49, 0.08);
  --c-scrim-88:   rgba(53, 20, 49, 0.88);
  --c-scrim-00:   rgba(53, 20, 49, 0);
  /* Mask channel only — never painted, the hue is irrelevant. Declared here
     so the "no literal colours outside :root" rule still holds. */
  --mask-opaque:  #000000;

  /* ========== 2. SURFACES ========== */
  --surface-dark:      var(--c-teal);
  --surface-dark-alt:  var(--c-plum);
  --surface-light:     var(--c-paper);
  --surface-raised:    var(--c-white);
  --surface-deep:      var(--c-ink);
  --surface-placeholder: var(--c-ink);

  /* ========== 3. TEXT ==========
     No --text-accent-on-light exists by design: neither amber nor orange
     clears AA as text on paper or white. Emphasis on light surfaces is
     carried by weight and by an orange rule, never by colour. */
  --text-on-light:       var(--c-ink);
  --text-on-light-muted: var(--c-ink-70);
  --text-on-dark:        var(--c-white);
  --text-on-dark-body:   var(--c-paper);
  --text-on-dark-muted:  var(--c-white-70);
  /* Amber text is legal on dark surfaces (7.0 / 6.64) but is NOT for content.
     Text that is meant to be read — phone numbers, codes, body copy, links —
     is never coloured; it earns emphasis from scale, weight and face. Reserve
     this for genuine highlights only. */
  --text-accent-on-dark: var(--c-amber);

  /* ========== 4. ACTION ==========
     Primary hover inverts rather than shifting hue — both states are 9.32:1.
     Orange was rejected as a hover: 4.01:1 fails AA at button label size. */
  --action-bg:       var(--c-amber);
  --action-fg:       var(--c-ink);
  --action-bg-hover: var(--c-ink);
  --action-fg-hover: var(--c-amber);

  --action-2-fg:         var(--c-ink);
  --action-2-border:     var(--c-ink);
  --action-2-bg-hover:   var(--c-ink);
  --action-2-fg-hover:   var(--c-paper);

  --action-2-fg-dark:       var(--c-white);
  --action-2-border-dark:   var(--c-white);
  --action-2-bg-hover-dark: var(--c-white);
  --action-2-fg-hover-dark: var(--c-ink);

  --control-bg: var(--c-ink);
  --control-fg: var(--c-white);

  /* ========== 5. STRUCTURE ========== */
  --accent-rule:      var(--c-orange);
  --divider-on-light: var(--c-ink-12);
  --divider-on-dark:  var(--c-white-12);
  --focus-ring:       var(--c-ink);
  --focus-ring-dark:  var(--c-amber);
  --shadow-header:    0 4px 16px var(--c-ink-14);
  --shadow-panel:     -4px 0 24px var(--c-ink-20);

  /* ========== 6. SCRIMS ==========
     Hero scrim was 0.90 -> 0.45 ink, which cast the photograph purple and
     flattened it. The photo's own median luminance under the text is 0.25,
     so 0.45 still gives white text 4.94:1 — past AA body — while letting
     the image through. Do not raise it again without re-measuring. */
  --hero-scrim-angle: 100deg;
  --hero-scrim-from:  var(--c-scrim-45);
  --hero-scrim-to:    var(--c-scrim-08);
  --tile-scrim-from:  var(--c-scrim-88);
  --tile-scrim-to:    var(--c-scrim-00);

  /* ========== 7. TYPOGRAPHY ========== */
  --font-display: 'Barlow Condensed', 'Arial Narrow', system-ui, sans-serif;
  --font-body:    'Barlow', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
  --font-mono:    ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;

  --text-base:    clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  --text-sm:      clamp(0.875rem, 0.85rem + 0.12vw, 0.9375rem);
  --text-h1:      clamp(2.25rem, 5vw + 1rem, 4.75rem);
  --text-h2:      clamp(1.75rem, 4vw + 1rem, 3.5rem);
  --text-h3:      clamp(1.25rem, 2vw + 1rem, 1.75rem);
  --text-h4:      clamp(1.125rem, 1.5vw + 0.5rem, 1.375rem);
  --text-display: clamp(1.875rem, 5vw + 1rem, 4rem);

  /* ========== 8. SPACING & LAYOUT ========== */
  --space-4: 4px;
  --space-8: 8px;
  --space-12: 12px;
  --space-16: 16px;
  --space-24: 24px;
  --space-32: 32px;
  --space-48: 48px;
  --space-64: 64px;
  --space-96: 96px;
  --space-128: 128px;

  --container-max: 1200px;
  --container-padding: clamp(20px, 5vw, 64px);
  --section-padding-v: clamp(64px, 10vw, 128px);

  /* Fixed-header clearance. Derived, not guessed:
     unscrolled = 77px logo + (16px padding x 2) = 109px
     scrolled   = 50px logo + (8px padding x 2)  =  66px
     If the logo height or header padding changes, change these too. */
  --header-h: 109px;
  --header-h-scrolled: 66px;

  /* ========== 9. MOTION — do not retune without checking main.js ========== */
  --transition-fast: 0.2s ease;
  --transition-normal: 0.4s ease;
  --transition-slow: 0.65s cubic-bezier(0.16, 1, 0.3, 1); /* From Shift EV reference */
}

/* ==========================================================================
   Construction gate (gate.html)
   Darkest surface on the site, knockout logo, amber as the single accent.
   Delete this block along with gate.html when the site goes live.
   ========================================================================== */
.gate-body {
  background-color: var(--surface-deep);
  color: var(--text-on-dark-body);
  min-height: 100vh;
  min-height: 100dvh;
}

.gate {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--space-32) var(--container-padding);
}

.gate-inner {
  width: 100%;
  max-width: 34rem;
  text-align: center;
}

.gate-logo {
  width: 100%;
  max-width: 300px;
  height: auto;
  margin: 0 auto var(--space-48);
}

.gate-eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-accent-on-dark);
  margin-bottom: var(--space-12);
}

.gate-title {
  font-size: clamp(2rem, 6vw + 1rem, 4rem);
  color: var(--text-on-dark);
  margin-bottom: var(--space-24);
}

.gate-sub {
  max-width: 46ch;
  margin: 0 auto var(--space-48);
  color: var(--text-on-dark-muted);
}

.gate-sub a {
  color: var(--text-on-dark-body);
  text-decoration: underline;
  text-decoration-color: var(--accent-rule);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}

.gate-form {
  border-top: 1px solid var(--divider-on-dark);
  padding-top: var(--space-32);
}

.gate-form label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  margin-bottom: var(--space-8);
  color: var(--text-on-dark-muted);
}

.gate-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-12);
  justify-content: center;
}

.gate-row input {
  flex: 1 1 12rem;
  min-width: 0;
  font: inherit;
  min-height: 44px;
  padding: var(--space-12);
  color: var(--text-on-dark);
  background-color: transparent;
  border: 1px solid var(--divider-on-dark);
  border-radius: 4px;
  transition: border-color var(--transition-fast);
}

.gate-row input:hover,
.gate-row input:focus {
  border-color: var(--accent-rule);
}

.gate-msg {
  min-height: 1.5em;
  margin-top: var(--space-12);
  font-size: var(--text-sm);
  color: var(--text-on-dark-muted);
}

.gate-msg.is-error {
  color: var(--text-accent-on-dark);
}

/* ==========================================================================
   Reset & Base Styles
   ========================================================================== */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  color: var(--text-on-light);
  background-color: var(--surface-light);
}

body {
  overflow-x: hidden;
  font-size: var(--text-base);
}

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

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

a:hover, a:focus {
  opacity: 0.8;
}

ul, ol {
  list-style: none;
}

button {
  cursor: pointer;
  border: none;
  background: none;
  font: inherit;
}

.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: var(--action-bg);
  color: var(--action-fg);
  padding: var(--space-8);
  z-index: 100;
  transition: top var(--transition-fast);
}

.skip-link:focus {
  top: 0;
}

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

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

.surface-dark :focus-visible,
.surface-dark-alt :focus-visible,
.surface-deep :focus-visible,
.site-footer :focus-visible,
.hero :focus-visible {
  outline-color: var(--focus-ring-dark);
}

/* ==========================================================================
   Typography
   ========================================================================== */
h1, h2, h3, h4, .display-text {
  font-family: var(--font-display);
  line-height: 1.08;
  font-weight: 700;
  margin-bottom: var(--space-16);
  overflow-wrap: break-word;
}

h1 { font-size: var(--text-h1); }
h2 { font-size: var(--text-h2); }
h3 { font-size: var(--text-h3); }
h4 { font-size: var(--text-h4); }
.display-text { font-size: var(--text-display); }

/* Section headings get real separation from their content. One value,
   everywhere, so the rhythm is the same on every page. */
section > .container > h2:first-child {
  margin-bottom: var(--space-48);
}

p { max-width: 68ch; }
.text-center p { margin-inline: auto; }

/* ==========================================================================
   Layout & Containers
   ========================================================================== */
.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-padding);
}

section {
  padding-block: var(--section-padding-v);
}

/* An interior page head sits directly above its own content. When both are on
   the same surface they read as one block, so the boundary must not carry two
   lots of padding. Scoped to .page-head deliberately — two *content* sections
   sharing a surface (Clients and References on Capabilities) still need their
   separation. */
.page-head.surface-light + .surface-light,
.page-head.surface-raised + .surface-raised {
  padding-top: 0;
}

/* Paper and white measure 1.12:1 — adjacent, they read as one continuous
   block. Any such boundary gets a hairline. General rule, replacing the
   one-off that used to live on .certifications. */
.surface-light + .surface-raised,
.surface-raised + .surface-light {
  border-top: 1px solid var(--divider-on-light);
}

/* Interior pages (About, Gallery, Contact) have no hero, so their first
   section would slide under the fixed header. Template rule — keep it. */
main > section:first-child:not(.hero) {
  padding-top: calc(var(--header-h) + var(--section-padding-v));
}

/* Anchor targets must clear the fixed header. Without this the skip link and
   the References nav link both land with the heading hidden behind the bar. */
[id] {
  scroll-margin-top: calc(var(--header-h-scrolled) + var(--space-24));
}

/* ==========================================================================
   Surface utilities
   Role-named, not colour-named — see decisions.md §3 for the page sequence.
   ========================================================================== */
.surface-light {
  background-color: var(--surface-light);
  color: var(--text-on-light);
}

.surface-raised {
  background-color: var(--surface-raised);
  color: var(--text-on-light);
}

.surface-dark {
  background-color: var(--surface-dark);
  color: var(--text-on-dark-body);
}

.surface-dark-alt {
  background-color: var(--surface-dark-alt);
  color: var(--text-on-dark-body);
}

/* Darkest band on the page. Used for the closing CTA: amber on ink is 9.32:1
   and reads as gold on near-black. Amber on plum measured fine (6.64) but
   plum is a wine red sitting next to a hot gold — adjacent hues, no
   separation, and it clashed. */
.surface-deep {
  background-color: var(--surface-deep);
  color: var(--text-on-dark-body);
}

.surface-deep h1, .surface-deep h2, .surface-deep h3,
.surface-deep h4, .surface-deep .display-text {
  color: var(--text-on-dark);
}

.surface-dark h1, .surface-dark h2, .surface-dark h3, .surface-dark h4, .surface-dark .display-text,
.surface-dark-alt h1, .surface-dark-alt h2, .surface-dark-alt h3, .surface-dark-alt h4, .surface-dark-alt .display-text {
  color: var(--text-on-dark);
}

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-12) var(--space-24);
  font-family: var(--font-body);
  font-weight: 600;
  letter-spacing: 0.01em;
  border-radius: 4px;
  transition: background-color var(--transition-fast), color var(--transition-fast), transform var(--transition-fast);
  text-align: center;
}

.btn-primary {
  background-color: var(--action-bg);
  color: var(--action-fg);
}

.btn-primary:hover, .btn-primary:focus {
  background-color: var(--action-bg-hover);
  color: var(--action-fg-hover);
  opacity: 1;
}

.btn-secondary {
  background-color: transparent;
  color: var(--action-2-fg);
  border: 2px solid var(--action-2-border);
}

.btn-secondary:hover, .btn-secondary:focus {
  background-color: var(--action-2-bg-hover);
  color: var(--action-2-fg-hover);
  opacity: 1;
}

.surface-dark .btn-secondary,
.surface-dark-alt .btn-secondary,
.site-footer .btn-secondary {
  color: var(--action-2-fg-dark);
  border-color: var(--action-2-border-dark);
}

.surface-dark .btn-secondary:hover,
.surface-dark-alt .btn-secondary:hover,
.site-footer .btn-secondary:hover {
  background-color: var(--action-2-bg-hover-dark);
  color: var(--action-2-fg-hover-dark);
}

/* ==========================================================================
   Header & Navigation
   Header is LIGHT — the logo is 73% black and measures 1.73:1 on teal.
   See decisions.md §3.
   ========================================================================== */
.site-header {
  position: fixed;
  top: 0;
  width: 100%;
  background-color: var(--surface-light);
  color: var(--text-on-light);
  z-index: 50;
  transition: padding var(--transition-normal), background-color var(--transition-normal);
  padding-block: var(--space-16);
}

.site-header.is-scrolled {
  padding-block: var(--space-8);
  box-shadow: var(--shadow-header);
}

.header-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.logo-img {
  display: block;
  height: 77px;
  width: auto;
  transition: height var(--transition-normal);
}

.site-header.is-scrolled .logo-img {
  height: 50px;
}

.menu-toggle {
  display: block;
  width: 44px;
  height: 44px;
  position: relative;
  z-index: 60;
}

.hamburger, .hamburger::before, .hamburger::after {
  display: block;
  width: 24px;
  height: 2px;
  background-color: var(--text-on-light);
  position: absolute;
  left: 10px;
  transition: transform var(--transition-fast), opacity var(--transition-fast);
}

.hamburger {
  top: 21px;
}

.hamburger::before {
  content: '';
  top: -8px;
  left: 0;
}

.hamburger::after {
  content: '';
  top: 8px;
  left: 0;
}

.menu-toggle[aria-expanded="true"] .hamburger {
  background-color: transparent;
}

.menu-toggle[aria-expanded="true"] .hamburger::before {
  transform: translateY(8px) rotate(45deg);
}

.menu-toggle[aria-expanded="true"] .hamburger::after {
  transform: translateY(-8px) rotate(-45deg);
}

.main-nav {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 100%;
  max-width: 400px;
  background-color: var(--surface-light);
  color: var(--text-on-light);
  padding: var(--space-96) var(--space-32) var(--space-32);
  /* Shift EV transition moved to bottom of file */
  z-index: 55;
  display: flex;
  flex-direction: column;
  gap: var(--space-32);
  box-shadow: var(--shadow-panel);
}

.nav-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-24);
}

.nav-list a {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-h3);
  font-weight: 700;
  min-height: 44px;
  display: flex;
  align-items: center;
}

.nav-cta {
  margin-top: auto;
  width: 100%;
}

@media (min-width: 900px) {
  .menu-toggle {
    display: none;
  }

  .main-nav {
    position: static;
    transform: none;
    background-color: transparent;
    padding: 0;
    width: auto;
    max-width: none;
    flex-direction: row;
    align-items: center;
    box-shadow: none;
  }

  .nav-list {
    flex-direction: row;
  }

  .nav-list a {
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 600;
    min-height: auto;
  }

  .nav-cta {
    margin-top: 0;
    width: auto;
  }
}

/* ==========================================================================
   Hero Section
   ONE scrim layer only. The duplicate .hero-bg::after gradient was removed —
   it double-darkened the photograph. Overlay colour and angle are tokenised.
   ========================================================================== */
.hero {
  position: relative;
  min-height: 82vh;
  display: flex;
  align-items: center;
  /* DELIBERATE EXCEPTION to the global section padding.
     The hero is full-bleed; it needs clearance for the fixed header, not
     section padding.
     Bottom padding is 0 on purpose. The header overlays the top 109px, so the
     visible area runs 109px -> bottom. Padding-top of 109 with align-items:
     center puts the content in the middle of THAT region. Any bottom padding
     pushes it back off-centre — 48px of it was lifting the text 24px high. */
  padding-block: var(--header-h) 0;
  overflow: hidden;
}

.hero-bg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
}

.hero-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Optional slow scale on load */
  animation: heroScale 10s ease-out forwards;
}

.hero-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(var(--hero-scrim-angle), var(--hero-scrim-from) 0%, var(--hero-scrim-to) 100%);
}

.hero-content {
  position: relative;
  z-index: 1;
  color: var(--text-on-dark);
}

.hero-text-block {
  max-width: 100%;
}

.hero-h1 {
  max-width: 18ch;
  color: var(--text-on-dark);
}

.hero-subline {
  font-family: var(--font-body);
  font-size: var(--text-h4);
  max-width: 52ch;
  margin-bottom: var(--space-32);
  color: var(--text-on-dark-body);
}

@keyframes heroScale {
  from { transform: scale(1); }
  to { transform: scale(1.04); }
}

/* ==========================================================================
   Storytelling Section (Scroll-Linked)
   ========================================================================== */
.story-container {
  display: flex;
  flex-direction: column;
}

.story-media-col {
  width: 100%;
}

/* MOBILE FIRST: no sticky. Pinning a 60vh image above the text left only
   ~240px to read in on a 390px phone. Sticky is re-enabled at >=900px below,
   where the two-column layout gives it room to work. */
.story-media-sticky {
  position: static;
  height: auto;
  aspect-ratio: 4 / 3;
  margin-bottom: var(--space-24);
}

.story-image-wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 4px;
  overflow: hidden;
  background-color: var(--surface-placeholder);
}

.story-img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.05);
  transition: opacity 0.6s ease, transform 1s ease;
  z-index: 1;
}

.story-img.active {
  opacity: 1;
  transform: scale(1);
  z-index: 2;
}

.bg-placeholder {
  background-color: var(--surface-dark);
}

.story-text-col {
  width: 100%;
}

/* MOBILE FIRST: blocks size to their content, and are fully legible.
   The 80vh min-height and 0.2 dim are part of the desktop scroll effect —
   they only make sense next to a pinned image, so they are applied at
   >=900px below. On mobile they produced 2326px of scroll for one sentence. */
.story-block {
  display: flex;
  flex-direction: column;
  justify-content: center;
  opacity: 1;
  padding-block: var(--space-32);
}

.story-block + .story-block {
  border-top: 1px solid var(--divider-on-light);
}

.spacer-100 {
  height: 0; /* desktop-only scroll padding — see the >=900px block */
}

@media (min-width: 900px) {
  .story-container {
    flex-direction: row;
    gap: var(--space-64);
  }

  .story-media-col, .story-text-col {
    width: 50%;
  }

  /* Sticky scroll effect — desktop only. Identical to the original. */
  .story-media-sticky {
    position: sticky;
    top: calc(var(--header-h-scrolled) + var(--space-32));
    height: 70vh;
    aspect-ratio: auto;
    margin-bottom: 0;
  }

  .story-block {
    min-height: 80vh;
    opacity: 0.2;
    transition: opacity 0.4s ease;
    padding-block: 0;
  }

  .story-block.active {
    opacity: 1;
  }

  .story-block + .story-block {
    border-top: none;
  }

  .spacer-100 {
    height: 100px;
  }
}

/* ==========================================================================
   Swiper Gallery Section
   ========================================================================== */
/* MOBILE FIRST: heading, carousel, then arrows. The arrows previously lived
   in a flex row with the heading and wrapped underneath it at narrow widths,
   landing between the heading and the images. */
/* minmax(0, 1fr), never a bare 1fr or an implicit auto track.
   Grid tracks and grid items both default to min-width:auto, meaning they
   size to their content. Swiper sets its slide widths FROM the container
   width, so an auto-sized track and a content-sized Swiper feed each other
   and the carousel grows without bound on every resize tick. */
.gallery-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-32);
}

/* One centred column at every width. The heading is centred, so a right-aligned
   arrow cluster beside it would be visibly off-axis — arrows sit centred below
   the carousel instead. */
.gallery-title {
  margin-bottom: 0; /* grid gap owns the spacing */
  text-align: center;
  max-width: 24ch;
  margin-inline: auto;
}

/* Was a full-width plum band with display-size type between two sections —
   it read as a divider rather than a statement. Demoted to an intro line
   under the heading: body scale, not display, so it supports the heading
   instead of competing with it. */
.gallery-intro {
  text-align: center;
  max-width: 52ch;
  margin: calc(var(--space-16) * -1) auto 0;
  font-size: var(--text-h4);
  line-height: 1.5;
  color: var(--text-on-dark-muted);
}

.gallery-nav {
  display: flex;
  gap: var(--space-12);
  justify-content: center;
  height: 44px;
}

/* The gallery sits on a dark surface, so the controls invert: ink-on-plum
   measures 1.4:1 and would be effectively invisible. */
.gallery-nav .swiper-button-prev,
.gallery-nav .swiper-button-next {
  position: static;
  width: 44px;
  height: 44px;
  background-color: var(--control-fg);
  color: var(--control-bg);
  border-radius: 50%;
  margin: 0;
}

.gallery-nav .swiper-button-prev::after,
.gallery-nav .swiper-button-next::after {
  font-size: 16px;
}

.gallery-nav .swiper-button-disabled {
  opacity: 0.3;
}

.gallery-swiper {
  overflow: visible; /* Let slides peek on edges */
  /* Required because this is a grid item. Without min-width:0 the item is
     sized by its content, and Swiper's JS-set slide widths then feed back
     into the container width — the carousel grows on every tick. */
  min-width: 0;
  width: 100%;
}

.solution-tile {
  position: relative;
  aspect-ratio: 4 / 3;
  background-color: var(--surface-placeholder);
  overflow: hidden;
  border-radius: 4px;
}

.solution-tile .tile-image-wrapper {
  width: 100%;
  height: 100%;
}

.solution-tile .bg-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow), opacity var(--transition-slow);
}

/* Hover zoom lives in the motion system block at the foot of this file —
   it was declared identically in both places. */

.solution-tile .tile-overlay {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  padding: var(--space-24);
  display: flex;
  align-items: flex-end;
  background: linear-gradient(to top, var(--tile-scrim-from) 0%, var(--tile-scrim-to) 60%);
  z-index: 2;
}

.tile-label {
  color: var(--text-on-dark);
  margin: 0;
  z-index: 2;
}

/* Replaces the inline margin-top that was on this element. */
.gallery-section .cta-wrapper {
  margin-top: var(--space-48);
}

.gallery-progress {
  width: 100%;
  height: 2px;
  background-color: var(--divider-on-dark);
  margin-top: var(--space-32);
  position: relative;
}

.gallery-progress-bar {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  background-color: var(--accent-rule);
  width: 0%; /* updated by JS */
}

/* ==========================================================================
   Interior pages — shared components
   Everything here follows the home page: same container, same section padding,
   same tile treatment as .solution-tile, same hover zoom.
   ========================================================================== */
.page-head h1 {
  max-width: 20ch;
}

.page-intro {
  font-size: var(--text-h4);
  line-height: 1.55;
  max-width: 62ch;
  margin-top: var(--space-24);
}

.service-block h2 {
  max-width: 24ch;
}

/* Same 4:3 frame and hover behaviour as the home page gallery tiles. */
.work-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-24);
}

@media (min-width: 640px) {
  .work-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1000px) {
  .work-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.work-figure {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: 4px;
  background-color: var(--surface-placeholder);
  margin: 0;
}

.work-figure img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow), opacity var(--transition-slow);
}

.work-figure:hover img {
  transform: scale(1.025);
  opacity: 0.8;
}

/* Projects placeholders. Scaffolding only — replaced wholesale when the real
   library arrives. Deliberately plain so nobody mistakes them for a design. */
.work-figure.is-placeholder {
  display: grid;
  place-items: center;
  background-color: var(--surface-raised);
  border: 1px dashed var(--divider-on-light);
}

.placeholder-label {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text-on-light-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* Clients. Same tile geometry as .work-grid, plus a caption underneath —
   the name is the point of this section, not the photograph. */
.client-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-24);
}

@media (min-width: 700px) {
  .client-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 1000px) {
  .client-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.client-item {
  margin: 0;
}

.client-item figcaption {
  margin-top: var(--space-12);
  font-family: var(--font-display);
  font-size: var(--text-h4);
  font-weight: 700;
  line-height: 1.15;
  color: var(--text-on-light);
}

/* Who we are — folded in from the legacy About Us page. */
.who-copy {
  max-width: 62ch;
  margin-bottom: var(--space-24);
}

/* Honeypot. Off-screen rather than display:none — some bots skip hidden
   fields, and screen readers get aria-hidden on the wrapper instead. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ==========================================================================
   Contact
   ========================================================================== */
.contact-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-64);
}

@media (min-width: 900px) {
  .contact-layout { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); }
}

.contact-form .field-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-24);
}

@media (min-width: 560px) {
  .contact-form .field-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.contact-form .field {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  margin-bottom: var(--space-24);
  max-width: none;
}

.contact-form label {
  font-weight: 600;
  font-size: var(--text-sm);
}

.contact-form input,
.contact-form textarea {
  font: inherit;
  color: var(--text-on-light);
  background-color: var(--surface-raised);
  border: 1px solid var(--divider-on-light);
  border-radius: 4px;
  padding: var(--space-12);
  min-height: 44px;
  width: 100%;
  transition: border-color var(--transition-fast);
}

.contact-form input:hover,
.contact-form textarea:hover {
  border-color: var(--accent-rule);
}

.contact-form textarea {
  resize: vertical;
}

.contact-aside-heading {
  font-size: var(--text-h4);
  margin-top: var(--space-32);
}

.contact-aside-heading:first-child {
  margin-top: 0;
}

.contact-phone {
  font-family: var(--font-display);
  font-size: var(--text-h3);
  font-weight: 700;
  text-decoration: underline;
  text-decoration-color: var(--accent-rule);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

.contact-figure {
  margin-top: var(--space-32);
  aspect-ratio: 3 / 4;
}

/* ==========================================================================
   Certifications
   Sits on --surface-raised (white): the badge artwork is third-party and
   most of it assumes a white ground. See decisions.md §3.
   ========================================================================== */
/* The badges render 74-155px wide (they are seven different aspect ratios at a
   fixed 120px height). Fixed column counts strand them in oversized cells —
   at 4 columns a 97x157 badge floats in 170px of dead space.
   Instead: a small basis that grows to fill, capped so a lone item on the last
   row cannot stretch. Fits all seven on one row at desktop, wraps naturally
   below, and justify-content centres any orphan.
   Fixed column counts were tried and rejected — see decisions.md §3c. */
/* Continuous ticker, not a stepped carousel.
   Stepped autoplay (advance, pause, advance) reads as glitchy; this runs at a
   constant linear speed so the row never stops. The badge set is duplicated in
   the markup and the track translates by exactly -50%, so the second set
   arrives exactly where the first started and the seam is invisible.

   NO Swiper here — it is pure CSS, so it cannot stutter and cannot fail if the
   CDN is blocked.

   --cert-scroll-duration is the one knob: higher is slower. */
/* Stays inside the container — a defined window that badges enter and leave,
   not a full-bleed strip running off the screen edges.
   Slot widths are set so roughly 2 / 3 / 4 badges are visible as the viewport
   grows, never the whole set at once.
   --cert-scroll-duration is the speed knob: higher is slower. */
.cert-marquee {
  /* Durations are per-breakpoint so the badges travel at a constant ~60px/sec
     regardless of slot size. The track gets longer as slots grow, so a single
     duration would make desktop noticeably faster than mobile. */
  --cert-scroll-duration: 19s;
  --cert-item-w: 140px;
  --cert-item-gap: var(--space-24);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, var(--mask-opaque) 5%, var(--mask-opaque) 95%, transparent);
          mask-image: linear-gradient(to right, transparent, var(--mask-opaque) 5%, var(--mask-opaque) 95%, transparent);
}

@media (min-width: 640px) {
  .cert-marquee {
    --cert-item-w: 180px;
    --cert-item-gap: var(--space-32);
    --cert-scroll-duration: 25s;
  }
}

@media (min-width: 900px) {
  .cert-marquee {
    --cert-item-w: 220px;
    --cert-item-gap: var(--space-48);
    --cert-scroll-duration: 31s;
  }
}

.cert-track {
  display: flex;
  width: max-content;
  animation: cert-scroll var(--cert-scroll-duration) linear infinite;
}

.cert-marquee:hover .cert-track,
.cert-marquee:focus-within .cert-track {
  animation-play-state: paused;
}

@keyframes cert-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* margin-right, not gap: the track must split into two identical halves for
   -50% to land cleanly. A flex gap leaves one fewer gap than items, so the
   halves differ and the loop visibly jumps. */
.cert-item {
  flex: 0 0 auto;
  width: var(--cert-item-w);
  margin-right: var(--cert-item-gap);
  text-align: center;
}

/* Box height tracks --badge-h so the labels always sit on one baseline. */
.cert-img-box {
  height: calc(var(--badge-h) + var(--space-32));
  display: grid;
  place-items: center;
  margin-bottom: var(--space-16);
}

/* Every badge renders at the SAME height; width follows from its own aspect
   ratio. One knob — --badge-h — scales the whole row against the section.

   Why aspect-ratio and not object-fit: none of these SVGs declare an intrinsic
   width or height, only a viewBox. Without an explicit ratio the browser has no
   size to work from, and `width:auto` leaves them unsized. Declaring the ratio
   per badge gives each <img> real dimensions, so nothing letterboxes, nothing
   needs clipping, and no overflow guard is required. */
.cert-img-box img {
  height: var(--badge-h);
  width: auto;
  aspect-ratio: var(--badge-ar);
  max-width: 100%;
}

/* Badge sizing.
   The seven badges run 0.62 to 1.29 in aspect ratio. Every one renders at the
   SAME height; its width follows from its own ratio. That is what makes the row
   read as aligned.

   THE ONE KNOB: --badge-h. Raise or lower it to scale all seven together
   against the section. 92px keeps the widest badge (118.8px) inside the
   narrowest cell (124px at a 320px viewport) and leaves 28px of clearance
   below the 120px box, so nothing can reach the label.

   Per-badge aspect ratios come straight from each file's viewBox — they are
   measurements, not adjustments. A new badge needs its ratio added below. */
/* --badge-h must stay under slot-width / 1.2912 (the widest badge's aspect),
   otherwise the landscape badges hit max-width:100% and shrink out of step
   with the rest, breaking the uniform height. */
.cert-item {
  --badge-h: 100px;
  --badge-ar: 1;
}

@media (min-width: 640px) {
  .cert-item { --badge-h: 130px; }
}

.cert-item[data-badge="state-hub"]       { --badge-ar: 1.0017; }
.cert-item[data-badge="federal-hubzone"] { --badge-ar: 1.2912; }
.cert-item[data-badge="bbb"]             { --badge-ar: 0.6169; }
.cert-item[data-badge="txdot"]           { --badge-ar: 1.2308; }
.cert-item[data-badge="dbe"]             { --badge-ar: 1.2308; }
.cert-item[data-badge="8a-certified"]    { --badge-ar: 1.2912; }
.cert-item[data-badge="nm-licensed"]     { --badge-ar: 0.9999; }

.cert-item h3 {
  font-size: var(--text-sm);
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--text-on-light);
}

/* ==========================================================================
   NAICS Codes  —  on --surface-dark, so amber is legal as accent text (7.0:1)
   ========================================================================== */
.naics-list {
  column-count: 1;
  column-gap: var(--space-48);
}

.naics-list li {
  margin-bottom: var(--space-16);
  break-inside: avoid;
}

/* Same rule as the phone number: these are codes people read, not highlights.
   The monospace face and the weight already separate them from the
   descriptions — colour was doing nothing the typography wasn't. */
.naics-number {
  color: var(--text-on-dark);
  font-family: var(--font-mono);
  font-weight: 700;
  display: inline-block;
  min-width: 80px;
}

@media (min-width: 640px) {
  .naics-list { column-count: 2; }
}

@media (min-width: 1200px) {
  .naics-list { column-count: 3; }
}

/* ==========================================================================
   References Section
   Orange appears here as a RULE only, never as text — it fails AA as body
   copy on every surface in the palette. See decisions.md §1.2.
   ========================================================================== */
/* Category tabs. Hidden in the markup and revealed by main.js, so if the
   script never runs the section falls back to every group listed in full. */
.ref-tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-12);
  margin-bottom: var(--space-48);
}

.ref-tab {
  padding: var(--space-12) var(--space-24);
  min-height: 44px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--text-on-light);
  background-color: transparent;
  border: 1px solid var(--divider-on-light);
  border-radius: 999px;
  transition: background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.ref-tab:hover {
  border-color: var(--accent-rule);
}

.ref-tab[aria-selected="true"] {
  background-color: var(--action-2-bg-hover);
  color: var(--action-2-fg-hover);
  border-color: var(--action-2-bg-hover);
}

.ref-group {
  margin-bottom: var(--space-64);
}

.ref-group:last-child {
  margin-bottom: 0;
}

/* Once tabs are driving the section, the per-group heading is redundant —
   the selected tab already names the category. */
.refs-tabbed .ref-group-heading {
  display: none;
}

.refs-tabbed .ref-group {
  margin-bottom: 0;
}

/* Panel transition. Cards rise in staggered when a tab is selected and drop
   back out when it is deselected. Scoped to .refs-tabbed, which only exists
   once main.js has taken over — with JS off the cards never get hidden. */
.refs-tabbed .ref-card,
.refs-tabbed .work-figure {
  opacity: 0;
  transform: translateY(14px);
}

.refs-tabbed .ref-group.is-active .ref-card,
.refs-tabbed .ref-group.is-active .work-figure {
  opacity: 1;
  transform: none;
  transition: opacity 0.4s ease, transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}

.refs-tabbed .ref-group.is-leaving .ref-card,
.refs-tabbed .ref-group.is-leaving .work-figure {
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}

/* Stagger. Cards enter left-to-right rather than all at once. */
.refs-tabbed .ref-group.is-active .ref-card:nth-child(1),
.refs-tabbed .ref-group.is-active .work-figure:nth-child(1) { transition-delay: 0.00s; }
.refs-tabbed .ref-group.is-active .ref-card:nth-child(2),
.refs-tabbed .ref-group.is-active .work-figure:nth-child(2) { transition-delay: 0.05s; }
.refs-tabbed .ref-group.is-active .ref-card:nth-child(3),
.refs-tabbed .ref-group.is-active .work-figure:nth-child(3) { transition-delay: 0.10s; }
.refs-tabbed .ref-group.is-active .ref-card:nth-child(4),
.refs-tabbed .ref-group.is-active .work-figure:nth-child(4) { transition-delay: 0.15s; }
.refs-tabbed .ref-group.is-active .ref-card:nth-child(5),
.refs-tabbed .ref-group.is-active .work-figure:nth-child(5) { transition-delay: 0.20s; }
.refs-tabbed .ref-group.is-active .ref-card:nth-child(6),
.refs-tabbed .ref-group.is-active .work-figure:nth-child(6) { transition-delay: 0.25s; }
.refs-tabbed .ref-group.is-active .ref-card:nth-child(n+7),
.refs-tabbed .ref-group.is-active .work-figure:nth-child(n+7) { transition-delay: 0.30s; }

.ref-group-heading {
  color: var(--text-on-light);
  border-bottom: 3px solid var(--accent-rule);
  padding-bottom: var(--space-8);
  margin-bottom: var(--space-24);
  display: inline-block;
}

.ref-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-24);
}

.ref-card {
  background-color: var(--surface-raised);
  border: 1px solid var(--divider-on-light);
  border-radius: 4px;
  padding: var(--space-24);
}

.ref-org {
  color: var(--text-on-light);
  margin-bottom: var(--space-8);
}

.ref-name {
  font-weight: 700;
}

.ref-title {
  color: var(--text-on-light-muted);
  font-size: var(--text-sm);
  margin-bottom: var(--space-8);
}

.ref-location {
  font-style: normal;
  font-size: var(--text-sm);
  color: var(--text-on-light-muted);
  margin-bottom: var(--space-8);
}

.ref-phone a {
  font-weight: 600;
  color: var(--text-on-light);
  text-decoration: underline;
  text-decoration-color: var(--accent-rule);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}

.ref-phone a:hover {
  text-decoration-thickness: 3px;
}

@media (min-width: 900px) {
  .ref-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1200px) {
  .ref-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ==========================================================================
   CTA Band
   ========================================================================== */
.cta-band-inner {
  text-align: center;
}

.cta-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-24);
  margin-top: var(--space-32);
}

/* Not coloured. This is a phone number people are meant to read, not a
   highlight — it carries its weight through scale and the display face. */
.cta-phone {
  font-family: var(--font-display);
  font-size: var(--text-h2);
  font-weight: 700;
  color: var(--text-on-dark);
}

@media (min-width: 640px) {
  .cta-actions {
    flex-direction: row;
    justify-content: center;
    gap: var(--space-48);
  }
}

/* ==========================================================================
   Footer
   Sits directly below the plum CTA band. Teal and plum measure 1.05:1
   against each other, so the seam needs an explicit rule or it vanishes.
   See decisions.md §3.
   ========================================================================== */
.site-footer {
  background-color: var(--surface-dark);
  color: var(--text-on-dark-body);
  border-top: 3px solid var(--accent-rule);
  padding-top: var(--section-padding-v); /* Same scale as every section */
}

.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-48);
  margin-bottom: var(--space-64);
}

.footer-logo {
  max-width: 280px;
}

.footer-heading {
  color: var(--text-accent-on-dark);
  font-size: var(--text-h4);
  margin-bottom: var(--space-24);
}

.footer-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-12);
}

.footer-contact p {
  margin-bottom: var(--space-24);
}

.footer-phone {
  font-family: var(--font-display);
  font-size: var(--text-h3);
  font-weight: 700;
}

.footer-copyright {
  border-top: 1px solid var(--divider-on-dark);
  padding-block: var(--space-32);
  font-size: var(--text-sm);
  color: var(--text-on-dark-muted);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-8);
  text-align: center;
}

.footer-copyright p {
  max-width: none; /* overrides the global 68ch measure */
  margin: 0;
}

.footer-credit {
  opacity: 0.75;
}

/* Build credit. Underlined so it reads as clickable without competing with
   the client's own contact details above it. */
.footer-credit a {
  color: var(--text-on-dark-body);
  text-decoration: underline;
  text-decoration-color: var(--accent-rule);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color var(--transition-fast);
}

.footer-credit a:hover,
.footer-credit a:focus {
  color: var(--text-accent-on-dark);
  opacity: 1;
}

@media (min-width: 640px) {
  /* Client's copyright left, build credit right — reads as a colophon bar
     rather than two stacked sentences. */
  .footer-copyright {
    flex-direction: row;
    justify-content: space-between;
    text-align: left;
  }
}

@media (min-width: 900px) {
  /* Three columns: brand, navigation, contact.
     The social column was removed — it pointed at Wix's own accounts. */
  .footer-grid {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr);
  }
}

/* ==========================================================================
   GSAP Initial States
   ========================================================================== */
/* We rely on main.js to set initial hidden states to prevent content disappearing if JS is disabled */

/* Reduced Motion Override */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  [data-sal],
  .gs-reveal,
  .gs-stagger,
  .gs-stagger-naics,
  .gs-stagger-ref {
    visibility: visible !important;
    opacity: 1 !important;
    transform: none !important;
  }

  .hero-img {
    animation: none;
  }

  /* The overlay must never be opaque for reduced-motion users. Without this
     they saw a blank screen: the GSAP block that cleared it never ran. */
  #page-transition-overlay,
  .js #page-transition-overlay {
    opacity: 0 !important;
  }

  /* Certification ticker stops and becomes a static wrapped row. The duplicate
     half is hidden so badges are not shown twice standing still. */
  .cert-track {
    animation: none !important;
    flex-wrap: wrap;
    justify-content: center;
    width: 100%;
  }
  .cert-marquee {
    width: auto;
    margin-inline: 0;
    -webkit-mask-image: none;
            mask-image: none;
  }
  .cert-item[aria-hidden="true"] {
    display: none;
  }

  /* Reference panels swap instantly — no rise, no fade. */
  .refs-tabbed .ref-card,
  .refs-tabbed .ref-group.is-active .ref-card,
  .refs-tabbed .ref-group.is-leaving .ref-card,
.refs-tabbed .ref-group.is-leaving .work-figure {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    transition-delay: 0s !important;
  }
}

/* ==========================================================================
   Shift EV Motion System (SAL & Overlays)
   ========================================================================== */

/* Full-viewport opaque div. If it is never cleared, the page renders blank.
   Scoped to .js for the same reason the SAL rules are: with JS off it must
   never become opaque in the first place. Cleared by main.js on load, by the
   reduced-motion override below, and by the .js timeout in <head>. */
#page-transition-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: var(--surface-light);
  z-index: 9999;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.85s ease;
}

.js #page-transition-overlay {
  opacity: 1; /* Starts opaque only once JS is confirmed */
}

.js #page-transition-overlay.is-loaded {
  opacity: 0;
}

/* SAL Base
   Scoped to .js — the class is set by an inline script in <head>. Without it
   nothing is ever hidden, so the page is fully readable when JS is off or when
   the animation CDNs are blocked. Animation behaviour is unchanged when JS runs. */
.js [data-sal] {
  opacity: 0;
  transition: opacity 0.65s ease, transform 0.65s ease;
}

.js [data-sal="fade--in"] {
  transform: translateY(40px);
}

.js [data-sal].sal-animate {
  opacity: 1;
  transform: none;
}

/* Image Zoom on Hover */
.image-zoom .bg-image {
  transition: 0.65s ease all;
}
.image-zoom:hover .bg-image {
  transform: scale(1.025);
  opacity: 0.8;
}

/* Mobile menu — slides in from the right.
   Was translateY(-100%), which dropped a right-anchored 400px drawer down from
   the top; the panel's own geometry (pinned right, capped width, shadow cast
   leftward) reads as a side drawer, so the motion now matches it.
   Duration and easing unchanged. */
.main-nav {
  transform: translateX(100%);
  transition: 0.85s ease transform;
}
.main-nav.is-open {
  transform: translateX(0);
}
.main-nav .nav-list {
  opacity: 0;
  transition: opacity 0.65s ease;
}
.main-nav.is-open .nav-list {
  opacity: 1;
  transition-delay: 0.35s;
}

@media (min-width: 900px) {
  .main-nav {
    transform: none;
    transition: none;
  }
  .main-nav .nav-list {
    opacity: 1;
    transition: none;
  }
}
