/* =========================================================
   Sideline Art — shared stylesheet
   Design tokens live in :root so the palette / spacing can be
   reused later by shop/product UI without a rebuild.
   ========================================================= */

@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@500;600;700;800;900&family=Inter:wght@400;500;600;700;800&family=Anton&family=Fraunces:ital,wght@0,500;0,600;1,500;1,600&display=swap');

:root {
  /* Brand palette — bold, energetic, not overly corporate. Dark theme:
     --cream is the page background (despite the name — kept to avoid
     touching every call site), --surface is one step up for cards,
     --charcoal/--charcoal-darker are deeper "sunken" panels. */
  --charcoal: #1b1d20;
  --charcoal-darker: #101113;
  --blue: #1c6dd0;
  --blue-light: #4f93f0;
  --blue-deep: #123a66;
  --surface: #34373c;
  --text: #eef1f4;
  --cream: #2a2d31;
  --white: #ffffff;
  --gray-700: #c3c9cf;
  --gray-500: #9aa1a8;
  --gray-200: #43474d;
  --gray-100: #34373c;

  /* ---- Editorial redesign layer (2026) ----
     A second palette used by the "premium creative-studio" sections
     (Work, Process, Story, Pricing, Trusted-by, Final CTA) — layered on
     top of the original dark-theme tokens above rather than replacing
     them, so the still-dark chrome (nav, footer, hero, contact form)
     doesn't need to be re-tuned. --ink is near-black text/backgrounds,
     --paper/--paper-dim are warm off-whites (not stark #fff) for the
     large light sections, --line is a hairline black divider at low
     opacity. Blue stays the same token/value — it just gets used far
     more sparingly against these, as an accent instead of a fill. */
  --ink: #101012;
  --ink-soft: #2b2b2e;
  --paper: #f6f2ea;
  --paper-dim: #ece6d9;
  --line: rgba(16, 16, 18, 0.14);
  --line-strong: rgba(16, 16, 18, 0.85);

  --font-display: 'Poppins', 'Segoe UI', Arial, sans-serif;
  --font-body: 'Inter', 'Segoe UI', Arial, sans-serif;
  /* Extra-heavy condensed display face, used sparingly for the handful
     of poster-scale moments (hero wordmark, CTA band headline, footer
     sign-off) that want real one-page-agency-site punch — everywhere
     else (nav, buttons, card titles, body headings) stays on the
     regular --font-display so the rest of the site's voice is untouched. */
  --font-display-xl: 'Anton', var(--font-display);
  /* Editorial serif/italic accent — used sparingly for a single word in
     a big headline (e.g. "SIDELINE"), pull-quotes, and section
     eyebrow numerals. Never body copy. */
  --font-serif: 'Fraunces', Georgia, serif;

  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 24px;

  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.30);
  --shadow-md: 0 10px 30px rgba(0, 0, 0, 0.38);
  --shadow-lg: 0 20px 50px rgba(0, 0, 0, 0.46);

  --max-width: 1180px;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--gray-700);
  background: var(--cream);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

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

a { color: inherit; text-decoration: none; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  color: var(--text);
  line-height: 1.15;
  margin: 0 0 0.5em;
}

h1 { font-size: clamp(2.1rem, 4.5vw, 3.4rem); font-weight: 800; }
h2 { font-size: clamp(1.6rem, 3vw, 2.3rem); font-weight: 700; }
h3 { font-size: 1.25rem; font-weight: 700; }

p { margin: 0 0 1em; }

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 24px;
}

.eyebrow {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--blue);
  margin-bottom: 0.6em;
}

/* =========================================================
   Editorial design system — shared across the redesigned Work,
   Process, Story, Pricing, Trusted-by and Final-CTA sections. Kept as
   generic, reusable primitives (a label, a rule, a huge headline, a
   serif accent, three button skins) so each section's own CSS only
   has to handle its unique layout, not reinvent type/line/button
   treatments every time.
   ========================================================= */

/* Warm off-white section — the "paper" half of the black/paper/blue
   system. Deliberately not pure #fff (var(--white)) — a warm, slightly
   dim off-white reads as editorial/print rather than "app UI". */
.section--paper {
  background: var(--paper);
  --text: var(--ink);
  color: var(--ink-soft);
}
.section--paper h1, .section--paper h2, .section--paper h3, .section--paper h4 { color: var(--ink); }
.section--paper-dim { background: var(--paper-dim); }

/* Solid black section — replaces the old blue-gradient .section--navy
   for the sections that want to be a real dark beat, so blue reads as
   an accent color rather than a background fill. */
.section--ink {
  background: var(--ink);
  color: rgba(246, 242, 234, 0.72);
}
.section--ink h1, .section--ink h2, .section--ink h3 { color: var(--paper); }
.section--ink .eyebrow { color: rgba(246, 242, 234, 0.6); }

/* Small uppercase "WORK — 02" style label: a number, an em-dash, a
   word, in that fixed order — the editorial-portfolio numbering motif
   used to open every redesigned section. Independent of .eyebrow (own
   class) since it always needs the number+rule structure, never just
   a bare word. */
.label-num {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 3px;
  text-transform: uppercase;
  margin-bottom: 1.4em;
}
.label-num__line { flex: none; width: 40px; height: 1px; background: currentColor; opacity: 0.5; }

/* Hairline divider — the "thin black divider line" motif. Full-width
   inside whatever container it's placed in; color inherits so it also
   works (lighter) on .section--ink. */
.hr-thin {
  border: none;
  border-top: 1px solid var(--line);
  margin: 0;
}
.section--ink .hr-thin { border-top-color: rgba(246, 242, 234, 0.18); }

/* Huge headline-as-graphic-element utility. Sized to occupy a real
   fraction of the viewport (per the brief: "25-40% of the viewport"
   for some section titles) rather than reading as an ordinary <h2>.
   Anton is already the site's poster-scale face (hero wordmark, CTA
   bands) — reused here instead of a third display font. */
.display-editorial {
  font-family: var(--font-display-xl);
  font-weight: 400;
  text-transform: uppercase;
  line-height: 0.94;
  letter-spacing: 0.2px;
  margin: 0;
}
/* Sparingly-used serif/italic accent for one word inside a headline or
   a pull-quote — the "expressive serif/italic typography" mixed in
   with the clean sans per the brief. Not uppercase (Fraunces' italic
   reads as a handwritten/editorial mark specifically because it isn't
   forced into caps), and dialed to var(--blue) so the one serif word
   in a black-on-white headline also doubles as the section's accent
   color hit. */
.accent-serif {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 500;
  text-transform: none;
  color: var(--blue);
}
.section--ink .accent-serif { color: var(--blue-light); }

.section {
  padding: clamp(48px, 7vw, 96px) 0;
}

.section-head {
  max-width: 680px;
  margin: 0 0 40px;
}
.section-head--center { margin-left: auto; margin-right: auto; text-align: center; }

/* ---------------- Buttons ---------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.95rem;
  padding: 14px 28px;
  border-radius: 999px;
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.btn:hover { transform: translateY(-2px); }

.btn--primary {
  background: linear-gradient(135deg, var(--blue-light), var(--blue));
  color: var(--white);
  box-shadow: var(--shadow-md);
}
.btn--primary:hover { box-shadow: var(--shadow-lg); }

.btn--ghost {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.5);
  color: var(--white);
}
.btn--ghost:hover { background: rgba(255, 255, 255, 0.12); }

.btn--outline {
  background: transparent;
  border-color: var(--text);
  color: var(--text);
}
.btn--outline:hover { background: var(--text); color: var(--charcoal-darker); }

/* ---- Editorial buttons ----
   Sharp corners, solid fills or a single hairline border — the
   opposite of the .btn--primary gradient pill above. Declared after
   .btn/.btn--primary/etc so these radius/background overrides
   actually win the cascade (equal specificity, source order decides).
   Reserved for the redesigned sections' CTAs, the nav CTA, and the
   hero's primary CTA. */
.btn--ink {
  background: var(--ink);
  color: var(--paper);
  border-radius: 3px;
  border: 1.5px solid var(--ink);
}
.btn--ink:hover { background: var(--ink-soft); }
/* Inverse of .btn--ink — solid paper on a dark background (nav, hero,
   footer, .section--ink) so the primary CTA always reads as a solid
   high-contrast chip, never a dark-on-dark shape. */
.btn--paper {
  background: var(--paper);
  color: var(--ink);
  border-radius: 3px;
  border: 1.5px solid var(--paper);
}
.btn--paper:hover { background: var(--white); }
.btn--ink-outline {
  background: transparent;
  color: var(--ink);
  border-radius: 3px;
  border: 1.5px solid var(--line-strong);
}
.btn--ink-outline:hover { background: var(--ink); color: var(--paper); }
/* On dark (.section--ink) backgrounds the outline needs to flip light. */
.section--ink .btn--ink-outline { color: var(--paper); border-color: rgba(246, 242, 234, 0.5); }
.section--ink .btn--ink-outline:hover { background: var(--paper); color: var(--ink); }
/* Text-only "button" — an underline that draws in on hover, no box at
   all. Used where even a bordered button would feel too heavy. */
.btn--line {
  background: none;
  border: none;
  border-radius: 0;
  padding: 4px 0;
  color: inherit;
  position: relative;
}
.btn--line::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1.5px;
  background: currentColor;
  transform: scaleX(1);
  transform-origin: left;
}
.btn--line:hover::after { transform: scaleX(0.4); }

/* ---------------- Header / Nav ---------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(42, 45, 49, 0.88);
  backdrop-filter: blur(8px);
  /* Was a solid grey border — on the Home hero it sat right on top of the
     background video and read as a hard grey seam across the footage.
     Transparent keeps the same box height/layout without the visible line;
     the blur + semi-transparent background still separate the header from
     whatever scrolls underneath. */
  border-bottom: 1px solid transparent;
}

.nav {
  display: flex;
  align-items: center;
  padding: 14px 24px;
  max-width: var(--max-width);
  margin: 0 auto;
}

.nav__logo-link { display: flex; align-items: center; }

/* ---------------- Wordmark (text-based brand mark) ---------------- */
.brand {
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1;
  display: inline-flex;
  align-items: baseline;
  gap: 0.35em;
}
.brand__main { color: var(--text); }
.brand__accent { color: var(--blue); letter-spacing: 0.12em; }
.brand--ondark .brand__main { color: var(--white); }
.brand--ondark .brand__accent { color: var(--blue-light); }

.brand--nav { font-size: 1.5rem; }
.brand--hero {
  font-size: clamp(2.2rem, 6vw, 3.6rem);
  margin-bottom: clamp(10px, 2vw, 16px);
  position: relative;
  z-index: 1;
}
.brand--footer { font-size: 1.2rem; margin-bottom: 14px; }

/* flex: 1 makes this ul claim all the space between the logo and the
   (now-standalone) CTA button; centering its own content within that
   space is what visually centers the tabs in the header. */
.nav__links {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(22px, 3.2vw, 44px);
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1;
}

/* Plain text links — no pill, no fill. Active/hover state reads via an
   underline sliding in + a subtle color shift instead of a solid
   background, closer to a minimal agency-site nav. */
.nav__links a {
  position: relative;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--gray-700);
  padding: 10px 2px;
  transition: color 0.15s ease;
}
.nav__links a::after {
  content: '';
  position: absolute;
  left: 2px;
  right: 2px;
  bottom: 6px;
  height: 2px;
  background: var(--blue-light);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.2s ease;
}
.nav__links a:hover { color: var(--white); }
.nav__links a:hover::after { transform: scaleX(1); }
.nav__links a.is-active { color: var(--white); font-weight: 700; }
.nav__links a.is-active::after { transform: scaleX(1); background: var(--blue); }

/* CTA now lives outside .nav__links (a direct sibling, not a li), so it
   sits after the centered tabs group and is naturally pinned to the
   header's right edge — this small margin is just breathing room from
   the tabs, not a positioning hack. */
.nav__cta { margin-left: 16px; }

/* Toggle button sits between the logo and the (out-of-flow-on-mobile)
   .nav__links on mobile; margin-left:auto pushes it — and the CTA
   right after it in source order — to the header's right edge, so the
   hamburger and "Get a Quote" cluster together while the logo stays
   pinned left. Has no visual effect on desktop, where display:none
   removes this element from flex layout entirely. */
.nav__toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 10px;
  margin-left: auto;
}
.nav__toggle span {
  width: 22px;
  height: 2.5px;
  background: var(--text);
  border-radius: 2px;
  transition: transform 0.25s ease, opacity 0.2s ease;
}
/* Hamburger -> X, driven off the aria-expanded state initNav() already
   sets on click — no extra JS/class needed. Middle bar fades out, top
   and bottom bars meet in the middle and rotate into an X, same
   "small, restrained, currentColor" icon language as the Materials
   accordion's own plus-to-x toggle. */
.nav__toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

@media (max-width: 860px) {
  .nav__toggle { display: flex; }
  /* Closed state stays in the layout (no display:none) so max-height +
     opacity can transition smoothly instead of snapping open/closed;
     visibility follows with a transition-delay so closed links aren't
     keyboard-focusable, but the delay is zeroed out on the way open so
     there's no perceptible lag showing the menu. */
  .nav__links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--surface);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0 16px;
    border-bottom: 1px solid var(--gray-200);
    box-shadow: var(--shadow-md);
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    visibility: hidden;
    pointer-events: none;
    transition: max-height 0.3s ease, opacity 0.22s ease, padding 0.3s ease, visibility 0s linear 0.3s;
  }
  .nav__links.is-open {
    max-height: 420px;
    opacity: 1;
    padding: 8px 16px 16px;
    visibility: visible;
    pointer-events: auto;
    transition: max-height 0.3s ease, opacity 0.22s ease, padding 0.3s ease, visibility 0s linear 0s;
  }
  /* Hairline divider between rows instead of the desktop underline
     hover — same thin-line language as .hr-thin used everywhere else
     on the site, just scoped to this dark dropdown's own palette. */
  .nav__links a {
    padding: 14px 10px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  }
  .nav__links li:last-child a { border-bottom: none; }
  .nav__links a::after { display: none; }
  /* CTA is no longer part of the collapsible dropdown (it's a sibling
     of .nav__links now), so it stays visible in the header bar at all
     times, right next to the hamburger — trimmed down slightly so the
     logo + button + hamburger trio fits comfortably on narrow screens. */
  .nav__cta { margin-left: 10px; padding: 8px 14px; font-size: 0.85rem; }
}
@media (max-width: 860px) and (prefers-reduced-motion: reduce) {
  .nav__links, .nav__links.is-open, .nav__toggle span { transition: none; }
}

/* ---------------- Hero ---------------- */
.hero {
  position: relative;
  overflow: hidden;
  padding: 32px 0 12px;
  /* Fallback background — same glow+grid pattern the hero always used.
     Now sits BEHIND the video/overlay (z-index:0) rather than being
     the primary background, so it's what shows through automatically
     if the video fails to load, autoplay is blocked with no poster
     yet rendered, or prefers-reduced-motion removes the video. No JS
     required for that baseline case — plain HTML5 <video> fallback
     behavior handles it. */
  background:
    radial-gradient(circle at 50% 14%, rgba(79, 147, 240, 0.24), transparent 40%),
    radial-gradient(circle at 15% 20%, rgba(28, 109, 208, 0.10), transparent 45%),
    radial-gradient(circle at 85% 0%, rgba(79, 147, 240, 0.14), transparent 40%),
    repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.025) 0, rgba(255, 255, 255, 0.025) 1px, transparent 1px, transparent 42px),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.025) 0, rgba(255, 255, 255, 0.025) 1px, transparent 1px, transparent 42px),
    var(--cream);
}

/* Background video layer — absolutely positioned to fill the hero,
   sitting at the bottom of the stack (z-index:0) so every existing
   piece of foreground content (.hero__inner, .hero__strips, .form-note,
   each given z-index:1 below) stays exactly where and how it was,
   just with a video playing behind it instead of a flat background. */
.hero__video-wrap {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
/* Respect prefers-reduced-motion: no autoplaying motion, just the
   poster frame (still shown via the <video poster> attribute) sitting
   under the overlay/gradient like a static hero image. */
@media (prefers-reduced-motion: reduce) {
  .hero__video { display: none; }
}
/* Dark navy/blue semi-transparent wash so the heading/tagline/CTAs
   stay easy to read over any footage, plus a soft blue radial glow on
   top tying the video into the site's existing blue accent language
   (matches the glow already used in .hero's own fallback background). */
.hero__video-overlay {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(16, 17, 19, 0.74) 0%, rgba(18, 58, 102, 0.56) 45%, rgba(16, 17, 19, 0.8) 100%),
    radial-gradient(circle at 50% 15%, rgba(79, 147, 240, 0.16), transparent 55%);
}

.hero__inner {
  position: relative;
  z-index: 1;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 24px;
  text-align: center;
}

.hero__tagline {
  /* Quieter by contrast against the much bigger wordmark below it —
     smaller, lighter weight, body font instead of display font, so the
     giant "SidelineArt" reads as the one dominant beat in the hero. */
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(1rem, 1.7vw, 1.15rem);
  line-height: 1.5;
  color: var(--gray-500);
  max-width: 560px;
  margin: 0 auto 16px;
  position: relative;
  z-index: 1;
  text-shadow: 0 2px 20px rgba(0, 0, 0, 0.4);
}
.hero__tagline strong { color: var(--white); font-weight: 600; }
/* Same italic-serif accent used across the redesigned sections below —
   .accent-serif defaults to the darker --blue, tuned for a light
   .section--paper background; brightened to --blue-light here to stay
   legible against the hero's dark video overlay. */
.hero__tagline .accent-serif { color: var(--blue-light); }

.hero__ctas {
  display: flex;
  gap: 14px;
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: 24px;
}

/* Sliding photo strips */
.hero__strips {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  mask-image: linear-gradient(90deg, transparent, black 8%, black 92%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, black 8%, black 92%, transparent);
}

.strip {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  width: max-content;
}

.strip--left { animation: slide-left 34s linear infinite; }
.strip--right { animation: slide-right 34s linear infinite; }

@keyframes slide-left {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@keyframes slide-right {
  from { transform: translateX(-50%); }
  to { transform: translateX(0); }
}

@media (prefers-reduced-motion: reduce) {
  .strip--left, .strip--right { animation: none; }
}

/* ---------------- Product-shaped carousel items ----------------
   Each .sign is a physical-signage silhouette built from CSS alone
   (no images): a lawn sign with stake legs, a tall senior banner,
   a grommeted sponsorship banner, a framed business sign, an
   arrow-clipped directional sign, and a wide fence/stadium banner.
   Shape (.sign--*) and color (.sign--c1..c6) are independent, so
   any shape can carry any palette variant. */
.sign {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.sign__face {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px 10px;
  border-radius: 5px;
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  text-align: center;
  overflow: hidden;
}

.sign__label {
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.58rem;
  letter-spacing: 0.1px;
  color: var(--white);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}

/* Real client banner photo filling a sign face (used on the two
   sponsor-banner carousel items). Positioned to cover the whole face
   including the padding meant for .sign__label, since a photo doesn't
   need that inset. Sits below the grommet ::after overlay (which is
   also position:absolute, added later in the DOM, so it still paints
   on top) so the grommets read as if pinned onto the real photo. */
.sign__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Shows the entire image with no cropping (letterboxed on whichever
   axis doesn't match the sign's proportions) instead of the default
   cover-and-crop behavior — the sign's own color gradient shows
   through behind the letterboxing. */
.sign__photo--contain { object-fit: contain; }

/* Color variants — reuse the site's charcoal/blue palette */
.sign--c1 .sign__face { background: linear-gradient(160deg, #2c3742, var(--charcoal-darker)); border: 1px solid rgba(79, 147, 240, 0.32); }
.sign--c2 .sign__face { background: linear-gradient(160deg, var(--blue), var(--blue-deep)); border: 1px solid rgba(255, 255, 255, 0.18); }
.sign--c3 .sign__face { background: linear-gradient(160deg, var(--blue-light), var(--blue)); border: 1px solid rgba(255, 255, 255, 0.25); }
.sign--c4 .sign__face { background: linear-gradient(160deg, #10151b, var(--charcoal)); border: 1px solid rgba(79, 147, 240, 0.3); }
.sign--c5 .sign__face { background: linear-gradient(160deg, var(--blue-deep), var(--charcoal-darker)); border: 1px solid rgba(79, 147, 240, 0.4); }
.sign--c6 .sign__face { background: linear-gradient(160deg, var(--blue), var(--blue-light)); border: 1px solid rgba(255, 255, 255, 0.22); }


/* Lawn sign — landscape face on two thin stake legs */
.sign--lawn { padding-bottom: 24px; }
.sign--lawn .sign__face { width: 94px; height: 56px; border-radius: 5px; }
.sign--lawn::before,
.sign--lawn::after {
  content: '';
  position: absolute;
  bottom: 0;
  width: 2px;
  height: 24px;
  background: linear-gradient(180deg, #cfd6dc, #8b939b);
  border-radius: 2px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.35);
}
.sign--lawn::before { left: calc(50% - 17px); }
.sign--lawn::after { left: calc(50% + 15px); }

/* Senior banner — tall vertical banner with a pole-pocket band + grommet */
.sign--senior .sign__face {
  width: 62px;
  height: 65px;
  border-radius: 4px;
  padding-top: 16px;
  align-items: flex-start;
  padding-bottom: 10px;
}
.sign--senior .sign__face::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 11px;
  background: rgba(0, 0, 0, 0.24);
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}
.sign--senior .sign__face::after {
  content: '';
  position: absolute;
  top: 4px; left: 50%;
  width: 5px; height: 5px;
  margin-left: -2.5px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 35%, #e8edf3, #8b939b 70%);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.3);
}
.sign--senior .sign__label { margin-top: auto; margin-bottom: 3px; }

/* Sized to the engagement-tea-party photo's exact aspect ratio
   (667:1000, i.e. width = height * 0.667) at the same 62px width as
   the other senior-shape items, so object-fit:contain fills the box
   edge-to-edge on both axes with zero letterboxing, instead of only
   filling height and leaving gaps on the sides. */
.sign--engagement .sign__face { height: 93px; }

/* Same technique as .sign--engagement above, for the demo senior
   banner design (747:1000, i.e. width = height * 0.747) — 62 / 0.747
   ≈ 83px. */
.sign--demo-banner .sign__face { height: 83px; }

/* Sponsorship banner — wide banner with four corner grommets */
.sign--sponsor .sign__face { width: 136px; height: 56px; border-radius: 3px; }
.sign--sponsor .sign__face::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    radial-gradient(circle, rgba(0, 0, 0, 0.5) 40%, transparent 42%),
    radial-gradient(circle, rgba(0, 0, 0, 0.5) 40%, transparent 42%),
    radial-gradient(circle, rgba(0, 0, 0, 0.5) 40%, transparent 42%),
    radial-gradient(circle, rgba(0, 0, 0, 0.5) 40%, transparent 42%);
  background-repeat: no-repeat;
  background-size: 7px 7px;
  background-position: 8px 8px, calc(100% - 8px) 8px, 8px calc(100% - 8px), calc(100% - 8px) calc(100% - 8px);
}

/* School / business sign — strong framed border */
.sign--business .sign__face {
  width: 99px;
  height: 60px;
  border-radius: 3px;
  border: 2px solid var(--blue-light);
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.45), inset 0 0 0 2px rgba(255, 255, 255, 0.06);
}

/* Directional sign — arrow-clipped edge */
.sign--directional .sign__face {
  width: 107px;
  height: 48px;
  padding-right: 16px;
  clip-path: polygon(0 0, 78% 0, 100% 50%, 78% 100%, 0 100%);
}

/* Fence / stadium banner — extra-wide banner with a row of mounting ties */
.sign--fence .sign__face { width: 155px; height: 47px; border-radius: 3px; padding-top: 5px; }
.sign--fence .sign__face::before {
  content: '';
  position: absolute;
  top: 5px; left: 0; right: 0;
  height: 6px;
  pointer-events: none;
  background-image: radial-gradient(circle, rgba(0, 0, 0, 0.5) 35%, transparent 38%);
  background-repeat: repeat-x;
  background-size: 17px 6px;
  background-position: 9px center;
}

@media (max-width: 640px) {
  .sign--lawn .sign__face { width: 76px; height: 47px; }
  .sign--senior .sign__face { width: 52px; height: 55px; }
  .sign--sponsor .sign__face { width: 109px; height: 46px; }
  .sign--business .sign__face { width: 81px; height: 50px; }
  .sign--directional .sign__face { width: 87px; height: 41px; }
  .sign--fence .sign__face { width: 122px; height: 33px; }
}

/* Declared after the combined mobile block above (same specificity as
   its .sign--senior rule, so source order decides) to correctly win
   and keep this photo's own aspect-ratio-matched height on mobile. */
@media (max-width: 640px) {
  .sign--engagement .sign__face { height: 78px; }
  .sign--demo-banner .sign__face { height: 70px; }
}

/* ---------------- Intro / general content blocks ---------------- */
.intro-grid {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: 48px;
  align-items: center;
}
@media (max-width: 860px) { .intro-grid { grid-template-columns: 1fr; } }

.stat-row {
  display: flex;
  gap: 32px;
  flex-wrap: wrap;
  margin-top: 24px;
}
.stat {
  font-family: var(--font-display);
}
.stat strong {
  display: block;
  font-size: 2.1rem;
  color: var(--blue);
  font-weight: 800;
}
.stat span {
  font-size: 0.85rem;
  color: var(--gray-500);
  text-transform: uppercase;
  letter-spacing: 1px;
}

.badge-panel {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: 32px;
  box-shadow: var(--shadow-md);
  border: 1px solid var(--gray-200);
}
.badge-panel ul { margin: 0; padding-left: 1.1em; }
.badge-panel li { margin-bottom: 10px; }

.timeline {
  display: flex;
  flex-direction: column;
  gap: 0;
  border-left: 3px solid var(--gray-200);
  margin-left: 10px;
  padding-left: 28px;
}
.timeline-item { position: relative; padding-bottom: 34px; }
.timeline-item:last-child { padding-bottom: 0; }
.timeline-item::before {
  content: '';
  position: absolute;
  left: -34px;
  top: 4px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--blue);
  border: 3px solid var(--cream);
  box-shadow: 0 0 0 2px var(--blue);
}
.timeline-item strong { color: var(--text); font-family: var(--font-display); }

/* ---------------- Contact / Quote form ---------------- */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1.15fr;
  gap: 48px;
  align-items: start;
}
@media (max-width: 900px) { .contact-grid { grid-template-columns: 1fr; } }

.contact-info-card {
  background: var(--charcoal-darker);
  color: var(--gray-200);
  border-radius: var(--radius-lg);
  padding: 32px;
}
.contact-info-card h3 { color: var(--white); }
.contact-info-list { list-style: none; padding: 0; margin: 18px 0 0; }
.contact-info-list li { display: flex; gap: 12px; margin-bottom: 16px; font-size: 0.94rem; color: var(--white); font-weight: 700; }
.contact-info-list svg { flex: none; width: 20px; height: 20px; color: var(--blue-light); margin-top: 2px; }

.form-card {
  background: var(--surface);
  border-radius: var(--radius-lg);
  padding: clamp(24px, 4vw, 40px);
  box-shadow: var(--shadow-md);
  border: 1px solid var(--gray-200);
}

.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 560px) { .form-row { grid-template-columns: 1fr; } }

.field { margin-bottom: 18px; }
/* Small-caps micro-label, letter-spaced — the same understated
   "prompt label above a big answer" convention the editorial redesign
   uses elsewhere (.label-num), instead of a plain bold sentence-case
   label. */
.field label {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.72rem;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--gray-500);
  margin-bottom: 8px;
}
/* Marks Full Name / Email / Sign Type / Project Details as required;
   Organization and Phone stay star-free since they're optional. Same
   red used for .form-error, so "required" and "something went wrong"
   read as the same color language on this dark form card. */
.required-star { color: #ff8a80; }
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  border: 1.5px solid var(--gray-200);
  font-family: var(--font-body);
  /* 1rem (16px), not 0.95rem — iOS Safari auto-zooms the page on focus
     for any input under 16px, which is jarring on a form this central
     to the site (the whole quote request). */
  font-size: 1rem;
  background: var(--charcoal-darker);
  color: var(--gray-700);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--blue);
  background: var(--charcoal);
}
.field textarea { resize: vertical; min-height: 120px; }

.form-note { font-size: 0.8rem; color: var(--gray-500); margin-top: 10px; }
/* Scoped rather than adding position/z-index to the shared .form-note
   rule (also used, unpositioned, on the Contact page's form). */
.hero .form-note { position: relative; z-index: 1; }

.form-success {
  display: none;
  align-items: center;
  gap: 10px;
  background: rgba(52, 199, 89, 0.12);
  border: 1px solid rgba(52, 199, 89, 0.4);
  color: #7be495;
  padding: 14px 16px;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  margin-bottom: 18px;
}
.form-success.is-visible { display: flex; }
/* Same treatment as .form-success, red instead of green — shown when
   the real Netlify submission fails, so a failed send is never
   silently swallowed into a fake "success" state. */
.form-error {
  display: none;
  align-items: center;
  gap: 10px;
  background: rgba(255, 69, 58, 0.12);
  border: 1px solid rgba(255, 69, 58, 0.4);
  color: #ff8a80;
  padding: 14px 16px;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  margin-bottom: 18px;
}
.form-error.is-visible { display: flex; }

/* Netlify honeypot field — real visitors never see or fill this in;
   spam bots that auto-fill every field do, and Netlify silently drops
   the submission when it's non-empty. Positioned off-screen rather
   than display:none/visibility:hidden, since more sophisticated bots
   skip fields hidden that way. */
.quote-form__hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.btn:disabled { opacity: 0.6; cursor: not-allowed; }

/* ---------------- Footer ---------------- */
.site-footer {
  background: var(--charcoal-darker);
  color: var(--gray-200);
  padding: 52px 0 24px;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 32px;
  margin-bottom: 36px;
}
@media (max-width: 720px) { .footer-grid { grid-template-columns: 1fr; } }

.footer-grid h4 {
  color: var(--white);
  font-size: 0.85rem;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  margin-bottom: 14px;
}
.footer-grid ul { list-style: none; padding: 0; margin: 0; }
.footer-grid li { margin-bottom: 10px; font-size: 0.9rem; }
.footer-grid a:hover { color: var(--blue-light); }
.footer-tagline { font-size: 0.88rem; color: var(--gray-500); max-width: 320px; }

.footer-bottom {
  border-top: 1px solid rgba(255,255,255,0.08);
  padding-top: 20px;
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  font-size: 0.8rem;
  color: var(--gray-500);
}

/* ---------------- Reveal-on-scroll ----------------
   All three variants below are pure-CSS, auto-playing entrance
   animations — no JS, no IntersectionObserver, no .is-visible class
   involved. That's a deliberate rewrite, not the original design:
   this whole system used to be JS-driven (initReveal() in main.js
   would add .is-visible once IntersectionObserver reported an
   element on-screen, and every one of these started at opacity:0
   until that happened). Two confirmed failures made that unsafe:
   (1) .reveal--up and .reveal--mask were only ever applied *without*
   the plain .reveal class alongside them, and initReveal() only ever
   selected `.reveal` (exact token) — so every .reveal--up headline
   (Process/Our Story/Pricing/Trusted By) and .reveal--mask element
   (Mark's Story photo) was NEVER observed at all, guaranteed
   invisible for every single visitor, not a rare edge case (this is
   what the user actually hit: "I don't see his picture"). (2) even
   correctly-observed plain .reveal elements were still at risk from
   a backgrounded/hidden document stalling IntersectionObserver
   callbacks (confirmed this session, see the .celebrate-in writeup).
   Fixed by making every variant an @keyframes animation that plays
   automatically on render and never animates `opacity` — only
   `transform`, so even a completely stuck/never-run animation still
   leaves the element fully visible, just slightly offset. */
@keyframes reveal-in { from { transform: translateX(-56px); } to { transform: translateX(0); } }
.reveal { animation: reveal-in 0.7s ease both; }

/* Headline variant — rises into position instead of sliding in
   sideways; used on the editorial sections' big display headlines. */
@keyframes reveal-up-in { from { transform: translateY(36px); } to { transform: translateY(0); } }
.reveal--up { animation: reveal-up-in 0.8s cubic-bezier(.22, 1, .36, 1) both; }

/* Image-mask reveal — was a JS/IntersectionObserver-driven "curtain"
   (clip-path 100% -> 0%, gated on .is-visible) sitting on Mark's
   photo wrapper in the Our Story section. That's exactly the bug
   class already fixed once for the Celebrate section: clip-path
   started at fully-clipped (the photo 100% hidden) and only ever
   un-clipped once IntersectionObserver added .is-visible — when that
   observer doesn't fire (confirmed this can happen — see the
   .celebrate-in comment further down for the document.hidden
   root-cause writeup), the photo just stays invisible forever. This
   is what caused the "I don't see his picture" report. Fixed the
   same way: a pure-CSS animation with zero JS/observer dependency,
   and critically the animated property (transform: scale) can never
   fully hide the element even if the animation itself gets stuck
   mid-flight — worst case the photo just sits very slightly zoomed
   in, never invisible. Put this class on the *wrapper* around an
   <img>, not the <img> itself (still true; the wrapper needs its own
   overflow:hidden, which .story-ed__photo already has). */
@keyframes reveal-mask-in {
  from { transform: scale(1.06); }
  to { transform: scale(1); }
}
.reveal--mask {
  animation: reveal-mask-in 1.1s cubic-bezier(.22, 1, .36, 1) both;
}

@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal--up, .reveal--mask { opacity: 1; transform: none; clip-path: none; animation: none; transition: none; }
}

/* =========================================================
   EDITORIAL REDESIGN — Work, Process, Our Story, Pricing,
   Trusted By, Final CTA. Built on the .section--paper/--paper-dim/
   --ink + .label-num/.hr-thin/.display-editorial/.accent-serif system
   defined near the top of this file (search "Editorial design
   system"). Nav, hero, footer and the contact form keep their
   original dark-theme CSS untouched.
   ========================================================= */

/* ---- Celebrate (Work) ----
   Editorial campaign composition: a two-column layout (headline+copy
   left, an art-directed overlapping photo stack right) over a
   textured warm-cream background carrying oversized, near-invisible
   graphic typography ("CELEBRATE" / "01") and a soft blue glow — see
   .celebrate__texture/__glow/__bg-word/__bg-num below. --celebrate-bg
   is scoped to this section (the brief's exact #F3F1EC) rather than
   touching the shared --paper token other sections rely on. */
.celebrate {
  --celebrate-bg: #f3f1ec;
  /* h1-h4 inherit color:var(--text), which defaults to the dark-theme's
     near-white — same override .section--paper normally provides for
     light sections, reproduced here since .celebrate no longer uses
     that class (it needs its own exact --celebrate-bg instead). */
  --text: var(--ink);
  position: relative;
  overflow: hidden;
  color: var(--ink-soft);
  background-color: var(--celebrate-bg);
}

/* Extremely subtle paper grain — a tiled feTurbulence SVG data-URI,
   no image request, multiplied at ~4% so it reads as tactile depth
   rather than visible noise. */
.celebrate__texture {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0.04;
  mix-blend-mode: multiply;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/rect%3E%3C/svg%3E");
  background-size: 100px 100px;
}

/* Soft atmospheric blue behind the photo side — deliberately just a
   big blurred radial gradient at low alpha, not a visible shape, so
   it reads as depth/light rather than a decoration. */
.celebrate__glow {
  position: absolute;
  right: -6%;
  top: 4%;
  width: 56%;
  height: 78%;
  background: radial-gradient(circle, rgba(28, 109, 208, 0.14), transparent 68%);
  filter: blur(70px);
  z-index: 0;
  pointer-events: none;
}

/* Oversized graphic typography — a design element, not a second
   headline. Sized wider than the viewport on purpose (see the
   comment on .celebrate for why letters crop at the edges) and kept
   under .celebrate__composition/z-index so real photos overlap it.
   [data-parallax] (initParallax() in main.js) gives it a slightly
   slower drift than the foreground while scrolling. */
.celebrate__bg-word {
  position: absolute;
  left: 3%;
  top: 9%;
  z-index: 0;
  font-family: var(--font-display-xl);
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(200px, 22vw, 350px);
  line-height: 0.82;
  color: var(--ink);
  opacity: 0.035;
  white-space: nowrap;
  pointer-events: none;
  user-select: none;
}
/* Outlined rather than filled, per the brief — text-stroke with a
   transparent fill; the @supports fallback (very old browsers only)
   swaps to a faint filled color instead of just disappearing. */
.celebrate__bg-num {
  position: absolute;
  right: -3%;
  bottom: -6%;
  z-index: 0;
  font-family: var(--font-display-xl);
  font-weight: 400;
  font-size: clamp(240px, 30vw, 420px);
  line-height: 0.75;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(16, 16, 18, 0.07);
  pointer-events: none;
  user-select: none;
}
@supports not (-webkit-text-stroke: 1px black) {
  .celebrate__bg-num { color: rgba(16, 16, 18, 0.05); }
}

.celebrate__label-row { position: relative; z-index: 1; padding-bottom: clamp(14px, 2vw, 22px); }

/* Pure-CSS, JS-independent entrance motion. Deliberately animates
   ONLY transform, never opacity — content is unconditionally
   opacity:1 (the plain CSS default; nothing here ever sets it to 0),
   so visibility can never depend on the animation actually running or
   completing. This is stricter than a typical "animate opacity 0->1
   with fill-mode both" approach: that pattern still fails if the
   animation is paused mid-flight and never resumes — which happens in
   real browsers when a page is in a backgrounded/hidden tab at load
   (confirmed while debugging this: document.hidden freezes CSS
   animations, and the exact same freeze is why IntersectionObserver-
   driven .reveal/.reveal--up elsewhere on the site could get stuck
   invisible — see the index.html comment for the full story). With
   opacity untouched, even a permanently-stuck animation just leaves
   the element sitting ~16px off its final position — never hidden. */
@keyframes celebrate-in {
  from { transform: translateY(16px); }
  to { transform: translateY(0); }
}
.celebrate-in {
  animation: celebrate-in 0.6s cubic-bezier(.22, 1, .36, 1) both;
}
@media (prefers-reduced-motion: reduce) {
  .celebrate-in { animation: none; }
}

/* Two-column campaign layout — headline+copy vs. the photo stack.
   align-items:start (not center) so the composition's height can
   exceed the intro column's without vertically squeezing either. */
.celebrate__layout {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(300px, 0.72fr) minmax(340px, 1fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: start;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: clamp(8px, 1.5vw, 16px) 24px clamp(40px, 6vw, 72px);
}

.celebrate__intro { padding-top: clamp(24px, 4vw, 56px); }
.celebrate__headline {
  font-size: clamp(2.8rem, 6.4vw, 5.4rem);
  line-height: 0.98;
  margin: 0 0 clamp(22px, 3.4vw, 36px);
}
.celebrate__headline-line { display: inline-block; }
.celebrate__accent { color: var(--blue); }

.celebrate__copy { max-width: 34ch; }
.celebrate__lede {
  margin: 0 0 10px;
  color: var(--ink-soft);
  font-size: clamp(1rem, 1.3vw, 1.15rem);
  line-height: 1.6;
}
.celebrate__sub {
  margin: 0;
  color: var(--ink-soft);
  opacity: 0.7;
  font-size: 0.85rem;
  letter-spacing: 0.3px;
}

/* ---- The photo stack ----
   .celebrate__composition is a defined-height stage; the two
   .celebrate__item photos (--dominant, --secondary) are absolutely
   positioned within it (not a grid) so they can overlap on purpose —
   "art-directed" rather than generated. A third item, --detail (an
   unphotographed "Custom Printed" placeholder), was removed entirely
   rather than left as an empty box. */
.celebrate__composition {
  position: relative;
  z-index: 1;
  min-height: clamp(460px, 46vw, 620px);
}

.celebrate__crop-mark {
  position: absolute;
  width: 18px;
  height: 18px;
  z-index: 4;
  opacity: 0.4;
  pointer-events: none;
}
.celebrate__crop-mark--tl { top: -10px; left: -10px; border-top: 1px solid var(--ink-soft); border-left: 1px solid var(--ink-soft); }
.celebrate__crop-mark--br { bottom: -10px; right: -10px; border-bottom: 1px solid var(--ink-soft); border-right: 1px solid var(--ink-soft); }

.celebrate__guideline {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 62%;
  width: 1px;
  background: rgba(16, 16, 18, 0.06);
  z-index: 0;
  pointer-events: none;
}

.celebrate__item {
  position: absolute;
  overflow: hidden;
  margin: 0;
  background: var(--paper-dim);
}
/* Narrowed from 67% — a touch smaller so the composition has more
   breathing room and the overlapping --secondary/--detail prints read
   more clearly as separate, intentional elements instead of packed
   tight against its edge. */
.celebrate__item--dominant { left: 0; top: 0; width: 62%; height: 100%; z-index: 1; }
/* This one item's caption sits below the photo instead of overlaid
   on it (see .celebrate__caption--under) — needs its own crop
   wrapper so the image can still fill/clip/hover-zoom the box while
   the figure itself allows the caption to sit past the box's own
   bottom edge instead of being clipped with it. */
.celebrate__item--dominant { overflow: visible; }
.celebrate__item--dominant .celebrate__item-crop { position: absolute; inset: 0; overflow: hidden; background: var(--paper-dim); }
/* --secondary's caption moved below the photo too (matching
   --dominant, see .celebrate__caption--under) — same crop-wrapper
   treatment for the same reason: overflow needs to open up on the
   figure so the caption isn't clipped, while the photo itself still
   needs its own clipped box. */
.celebrate__item--secondary { overflow: visible; }
.celebrate__item--secondary .celebrate__item-crop { position: absolute; inset: 0; overflow: hidden; }
/* Moved up off the composition's bottom edge (was bottom:0) now that
   --detail (the small overlapping "Custom Printed" placeholder that
   used to sit above/right of this photo) has been removed — there's
   real open space above it to move into instead of just floating
   lower in an otherwise-empty column. */
.celebrate__item--secondary {
  right: 0;
  bottom: 14%;
  width: 42%;
  height: 52%;
  z-index: 2;
}

.celebrate__item img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transition: transform 0.8s cubic-bezier(.22, 1, .36, 1), filter 0.4s ease;
}
.celebrate__item img { object-fit: cover; }
/* Default center crop cut the man's head off (the box is wider than
   the photo, so cover crops top/bottom) — shifted up so his full
   head clears the frame with breathing room above it, trading some
   of the couple's feet/the lower stairs for it instead. (No
   data-parallax on this <img> — its constant scale(1.08) from
   initParallax() was quietly re-zooming past this crop and pushing
   his head back out of frame even after the object-position fix.) */
.celebrate__item--dominant img { object-position: center 15%; }
.celebrate__item:hover img { transform: scale(1.04); }
.celebrate__item:hover img { filter: contrast(1.04); }
@media (prefers-reduced-motion: reduce) {
  .celebrate__item img { transition: none; }
}
@media (hover: none) {
  .celebrate__item:hover img { transform: none; }
  .celebrate__item:hover img { filter: none; }
}

/* Understated caption — small uppercase editorial label over a soft
   scrim, not a solid label chip like the rest of the site's
   .work-feature__label used to be; a thin blue rule is the section's
   one deliberate brand-blue accent, repeated small on every item
   rather than one big blue fill. */
.celebrate__item::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(16, 16, 18, 0) 55%, rgba(16, 16, 18, 0.55) 100%);
  pointer-events: none;
}
/* No darkening scrim needed on any of the three items now — all their
   captions sit below the photo, not overlaid on top of it. */
.celebrate__item--dominant::after,
.celebrate__item--secondary::after { background: none; }
.celebrate__caption {
  position: absolute;
  z-index: 1;
  left: 14px;
  bottom: 12px;
  padding-left: 10px;
  border-left: 2px solid var(--blue-light);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.78rem;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--white);
}
/* Sits directly under the photo instead of overlaid on it — small,
   quiet, dark-on-paper instead of white-on-scrim. Used by all three
   items now. Centered under the photo (left: 50% + translateX, not
   left: 0) so the caption reads as centered under the image rather
   than left-aligned with it. */
.celebrate__caption--under {
  position: absolute;
  z-index: 1;
  left: 50%;
  transform: translateX(-50%);
  bottom: auto;
  top: 100%;
  margin-top: 10px;
  /* Without an explicit width, a position: absolute element with only
     left (no right) set gets shrink-to-fit sized against the space
     from that left edge to the containing block's own right edge —
     half the figure's width here, since left is 50% — not its full
     preferred text width. Under --detail's narrower box that halved
     space was too little, wrapping "Demo Senior Banner" onto 3 lines
     well before it ran out of actual room; nowrap forces the true
     one-line width so translateX(-50%) centers it correctly. */
  white-space: nowrap;
  /* Opaque backdrop, matching the section's own background exactly
     (--celebrate-bg), so the giant faint "01" watermark behind the
     composition (.celebrate__bg-num, z-index: 0) doesn't show its
     outline strokes through this caption — that's what was reading as
     faded lines under/behind the "Made for Their Moment" caption. */
  background: var(--celebrate-bg);
  padding: 3px 10px 3px 10px;
  border-left: 2px solid var(--blue-light);
  font-size: 0.68rem;
  color: var(--ink-soft);
}

/* Tablet — stack the two columns but keep the composition's
   overlapping art-direction (just resized), per the brief's "preserve
   the asymmetrical hierarchy" on tablet specifically. */
@media (max-width: 900px) {
  .celebrate__layout { grid-template-columns: 1fr; }
  .celebrate__intro { padding-top: clamp(8px, 2vw, 16px); }
  .celebrate__composition { min-height: clamp(420px, 62vw, 560px); margin-top: clamp(28px, 4vw, 40px); }
  /* Pushed down (top:5% -> ~38%) so it sits behind the photo
     composition instead of behind the headline — at this narrower,
     stacked layout the headline is proportionally much smaller than
     the giant word, so overlapping it read as visual clutter instead
     of a subtle watermark. */
  .celebrate__bg-word { top: 38%; font-size: clamp(120px, 22vw, 220px); }
  .celebrate__bg-num { font-size: clamp(200px, 34vw, 320px); }
}

/* Mobile — the brief is explicit: simplify rather than force the
   desktop composition into a narrow screen. Overlap/absolute
   positioning is dropped entirely (each item back to normal document
   flow with its own aspect-ratio, not a uniform list), crop marks and
   the guideline hide (too fussy at this size), and the giant
   background type/number shrink and pull toward the edges so they
   stay visible without ever forcing horizontal scroll. */
@media (max-width: 640px) {
  .celebrate__layout { padding: 0 20px clamp(32px, 8vw, 48px); gap: 28px; }
  .celebrate__intro { padding-top: 0; }
  .celebrate__composition {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-height: 0;
  }
  .celebrate__item {
    position: static;
    width: 100%;
    height: auto;
    border: none;
  }
  /* .celebrate__item-crop is position: absolute at desktop, which
     takes it out of normal flow — fine there since the caption
     overlays the image, but at mobile the caption needs to sit in
     real flow BELOW the image. An absolutely-positioned crop div
     contributes no flow height, so a static-flow caption would just
     render at the figure's top, overlapping the image instead of
     following it. Making the crop div itself a normal-flow block
     with its own aspect-ratio (image sizing moves here from the
     figure) fixes that: it now occupies real space, and the caption
     flows naturally after it. */
  .celebrate__item--dominant .celebrate__item-crop,
  .celebrate__item--secondary .celebrate__item-crop { position: relative; inset: auto; aspect-ratio: 4 / 5; }
  /* Static flow here (see comment above) means left/transform no
     longer apply — center via text-align + a matching border-left
     removal instead (a left accent bar reads oddly once the text
     itself is centered under the full-width photo). Applies to all
     three captions, which share this class. */
  /* Static flow (see above) doesn't have the absolute-positioning
     shrink-to-fit quirk nowrap works around at desktop — back to
     normal here so a caption too long for a narrow phone screen can
     still wrap instead of overflowing off-screen. */
  .celebrate__caption--under { position: static; transform: none; margin-top: 10px; text-align: center; border-left: none; padding-left: 0; white-space: normal; }
  .celebrate__crop-mark, .celebrate__guideline { display: none; }
  /* Same reasoning as the tablet override above, pushed further since
     mobile's headline occupies even more of the narrow viewport
     proportionally — this keeps the giant word behind the photos,
     not competing with "MAKE THEIR MOMENT UNFORGETTABLE." */
  .celebrate__bg-word { top: 44%; left: -3%; font-size: clamp(70px, 26vw, 130px); }
  .celebrate__bg-num { right: -6%; bottom: -4%; font-size: clamp(120px, 46vw, 220px); }
  .celebrate__glow { width: 80%; height: 50%; }
}

/* ---- Process ---- */
.process { text-align: left; }
.process__headline { font-size: clamp(1.8rem, 4.4vw, 3.4rem); max-width: 20ch; }
.process__rule { margin: clamp(20px, 3vw, 32px) 0; }

/* Headline + pull-quote share one row, quote sitting in the blank
   space the (intentionally narrow) headline leaves to its right.
   flex-wrap lets the quote drop below the headline once the row gets
   too tight to hold both — same breakpoint the border/padding swap
   below reacts to. */
.process__head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: clamp(24px, 4vw, 48px);
}
.process__head .process__headline { margin: 0; }
.process__quote {
  flex: 0 1 auto;
  margin: 0;
  padding-left: clamp(20px, 2.4vw, 32px);
  border-left: 1px solid rgba(246, 242, 234, 0.18);
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(0.85rem, 1.3vw, 1.3rem);
  line-height: 1.4;
  color: rgba(246, 242, 234, 0.88);
}
/* Kept to one line (not the usual pull-quote wrap) — it's meant to
   sit as a single strip in the blank space beside the headline, not
   read as its own paragraph. Reverts to normal wrapping under 700px,
   where it drops below the headline onto its own full-width row. */
.process__quote p { margin: 0 0 0.5em; white-space: nowrap; }
.process__quote-attribution {
  display: block;
  text-align: center;
  font-family: var(--font-display);
  font-style: normal;
  font-weight: 700;
  font-size: 0.7rem;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--blue-light);
}
@media (max-width: 700px) {
  .process__quote {
    border-left: none;
    padding-left: 0;
    padding-top: 0.9em;
    border-top: 1px solid rgba(246, 242, 234, 0.18);
  }
  .process__quote p { white-space: normal; }
}

.process__steps {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
}
.process__step {
  padding: 4px clamp(16px, 2vw, 28px) 4px 0;
  border-left: 1px solid rgba(246, 242, 234, 0.18);
  padding-left: clamp(16px, 2vw, 28px);
}
.process__step:first-child { border-left: none; padding-left: 0; }
.process__num {
  display: block;
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 500;
  font-size: 2.2rem;
  color: var(--blue-light);
  margin-bottom: 14px;
}
.process__step h3 { color: var(--paper); font-size: 1.1rem; margin-bottom: 8px; }
.process__step p { font-size: 0.92rem; line-height: 1.6; margin: 0; }

/* Steps 01-03 (Photograph/Design/Produce) carry a looping background
   video — see initLoopedVideos() in main.js, which resets each
   [data-loop-seconds] video to 0 on its own timeupdate rather than
   relying on the source clip's own length, so all three read as a
   clean 4s loop regardless of how long the original footage is.
   .process__step-scrim is a flat dark tint (not just a bottom
   gradient) since the num/heading/paragraph need to stay legible
   across the whole box, not just its lower edge. */
.process__step--video {
  position: relative;
  overflow: hidden;
  min-height: clamp(240px, 26vw, 340px);
  /* Video/scrim need to fill the box edge-to-edge, not inset by the
     shared .process__step padding — that padding moves onto
     .process__step-content (the text) instead. */
  padding: 0;
}
.process__step-video {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.process__step-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: rgba(16, 16, 18, 0.6);
  pointer-events: none;
}
.process__step-content {
  position: relative;
  z-index: 2;
  padding: clamp(20px, 2.4vw, 28px) clamp(16px, 2vw, 28px);
}
/* Same fallback as the hero video: no autoplaying motion — the box
   just shows the section's own dark background under the scrim,
   which is already legible on its own. */
@media (prefers-reduced-motion: reduce) {
  .process__step-video { display: none; }
}

@media (max-width: 860px) {
  .process__steps { grid-template-columns: 1fr; gap: 28px; }
  .process__step { border-left: none; border-top: 1px solid rgba(246, 242, 234, 0.18); padding: 24px 0 0; }
  .process__step:first-child { border-top: none; padding-top: 0; }
  .process__step--video { padding: 0; border-top: none; min-height: clamp(220px, 60vw, 320px); }
  .process__step--video .process__step-content { padding: 20px; }
}

/* ---- Our Story ---- */
.story-ed { position: relative; overflow: hidden; padding-top: clamp(32px, 4.5vw, 56px); padding-bottom: clamp(24px, 3vw, 40px); }
.story-ed__head { position: relative; z-index: 1; padding-bottom: clamp(8px, 1.4vw, 16px); }
/* Ties the label to the composition below it instead of leaving it
   floating on its own — the same hairline the Process section
   already uses under its own headline, reused here rather than
   inventing a new device. */
.story-ed__rule { margin-top: clamp(14px, 2vw, 22px); }

/* Extremely subtle paper grain, same technique/values as
   .celebrate__texture — the section reads slightly flat as one big
   plain --paper field otherwise. Kept almost imperceptible on purpose
   (see the brief: "barely visible", not a visible pattern). */
.story-ed__texture {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0.035;
  mix-blend-mode: multiply;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/rect%3E%3C/svg%3E");
  background-size: 100px 100px;
}

.story-ed__grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(300px, 1fr) minmax(540px, 720px) minmax(420px, 48vw);
  column-gap: clamp(24px, 3.2vw, 44px);
  align-items: start;
}

/* Portrait wrapper: the definite width here (not on .story-ed__photo
   itself) is what anchors it to the left edge of its track — same
   "definite width = left-anchored by default" mechanism as before.
   82% (down from the old 90%) + a shorter photo box below combine
   for a real reduction in the portrait's visual weight, not just a
   number tweak on one axis. */
.story-ed__portrait { width: 82%; }
.story-ed__photo {
  position: relative;
  overflow: hidden;
  width: 100%;
  /* Shorter than before (was clamp(380px, 60vw, 680px)) — that tall a
     box against a square source image forced object-fit: cover to
     crop in tight on just his face. A shorter box needs less crop,
     giving real breathing room around his head/shoulders. */
  min-height: clamp(320px, 46vw, 560px);
  border-radius: 4px;
  background: var(--paper-dim);
  /* Same treatment on both photos — depth without turning them into
     cards: a soft, wide, low-alpha shadow (not a hard drop-shadow)
     plus a hairline that's barely there. */
  box-shadow: 0 18px 40px -12px rgba(16, 16, 18, 0.16);
  border: 1px solid rgba(16, 16, 18, 0.06);
  /* `scale` is a separate CSS property from `transform` (not a
     transform function) specifically so this hover can't collide
     with the story-in entrance animation (which persists its final
     `transform` value via fill-mode: both) or with initParallax()'s
     own inline `transform` on the <img> — both keep animating
     `transform`, this only ever touches `scale`. */
  transition: scale 0.3s ease;
}
.story-ed__photo:hover { scale: 1.02; }
/* [data-parallax] gets its transform from initParallax() in main.js —
   the base translateY(0) here is just so the property exists before
   JS runs (avoids a one-frame layout jump on first paint). */
.story-ed__photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center 15%; will-change: transform; }
/* Small, minimal editorial caption — the only two lines here, sans
   bold name + serif italic role, echoing the same sans/serif pairing
   the site already uses for headline + accent-serif elsewhere. */
.story-ed__caption {
  margin: 12px 0 0;
  font-size: 0.8rem;
  line-height: 1.4;
  text-align: center;
}
.story-ed__caption strong {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--ink);
}
.story-ed__caption span {
  display: block;
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 500;
  color: var(--ink-soft);
}

/* Second photo, on the other side of the text column — enlarged and
   deliberately NOT top-aligned with the headline/portrait (the
   margin-top below), so the composition reads as art-directed rather
   than three columns mechanically lined up. Its box's aspect-ratio
   still matches the source image exactly (660x438) so nothing is
   cropped out of frame — see the box-vs-image-aspect note this was
   worked out from. */
.story-ed__photo--secondary {
  width: 100%;
  min-height: 0;
  aspect-ratio: 660 / 438;
  margin-top: clamp(36px, 5vw, 72px);
}

.story-ed__content {
  --story-content-pad-x: clamp(20px, 3vw, 32px);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding: clamp(8px, 1.6vw, 20px) var(--story-content-pad-x) clamp(24px, 3vw, 40px);
}
.story-ed__headline {
  font-size: clamp(2.6rem, 6.6vw, 5.6rem);
  line-height: 0.84;
  margin-bottom: 0.5em;
}
/* "sideline." reads as the section's visual signature — sized up more
   than the rest of the headline (was 1.08em) for stronger contrast
   between the two typefaces, and pulled up slightly with a negative
   margin so it overlaps the line above by a hair — a small
   deliberate break in the grid instead of two rigidly stacked lines. */
.story-ed__headline .accent-serif { display: inline-block; font-size: 1.16em; margin-top: -0.1em; }
.story-ed__body { max-width: 48ch; }
.story-ed__body p { font-size: 1.1875rem; line-height: 1.85; color: var(--ink); }
/* First sentence reads as a lede, editorial-style; the second steps
   down in size/weight/color so the hierarchy between "the statement"
   and "the supporting detail" is unmistakable at a glance. */
.story-ed__body p:first-child { font-weight: 600; }
.story-ed__body p:nth-child(2) { font-size: 1rem; font-weight: 400; color: var(--ink-soft); }

/* Understated "we do the whole process" rail — small blue step
   numbers + dark uppercase labels + thin arrows, generous spacing.
   Still plain inline text, never cards/buttons. Left edge stays
   aligned with the headline/body text above it; only the RIGHT edge
   bleeds past the content column's own padding (negative right
   margin canceling --story-content-pad-x), so all four steps get
   extra room to sit on one line with real breathing room instead of
   competing with the narrow prose column for width and wrapping
   "04 Install" onto an orphan second line. */
.story-ed__process {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  column-gap: 26px;
  row-gap: 10px;
  margin-top: 2em;
  margin-right: calc(-1 * var(--story-content-pad-x));
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.72rem;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.story-ed__process-step { display: inline-flex; align-items: baseline; gap: 7px; }
.story-ed__process-num { color: var(--blue); }
.story-ed__process-arrow { color: var(--blue); font-weight: 400; opacity: 0.75; }

/* Restrained scroll-reveal, scoped to this section — reuses the
   sitewide safety rule (transform-only, animation-fill-mode: both,
   so content already renders even if the animation never runs) at a
   smaller amplitude/duration than the global .reveal system, with a
   slight stagger across the five pieces. One shared keyframe driven
   by custom properties per element, rather than five near-duplicate
   @keyframes blocks. */
@keyframes story-in {
  from { transform: translate(var(--story-in-x, 0), var(--story-in-y, 0)); }
  to { transform: translate(0, 0); }
}
/* Portrait arrives from the left, workspace photo from the right —
   mirrored horizontal directions framing the headline in the middle,
   per the brief. */
.story-ed__portrait { --story-in-x: -22px; animation: story-in 0.5s cubic-bezier(.22, 1, .36, 1) both; }
.story-ed__headline { --story-in-y: 18px; animation: story-in 0.5s cubic-bezier(.22, 1, .36, 1) both 0.08s; }
.story-ed__body { --story-in-y: 18px; animation: story-in 0.5s cubic-bezier(.22, 1, .36, 1) both 0.16s; }
.story-ed__photo--secondary { --story-in-x: 22px; animation: story-in 0.55s cubic-bezier(.22, 1, .36, 1) both 0.12s; }
/* Process rail reveals one step at a time (not the whole line at
   once) — :nth-of-type counts by tag (all four steps and all three
   arrows are <span>s), so (1)/(3)/(5)/(7) lands on the four step
   spans specifically, skipping the arrows between them. */
.story-ed__process-step { --story-in-y: 14px; animation: story-in 0.4s cubic-bezier(.22, 1, .36, 1) both; }
.story-ed__process-step:nth-of-type(1) { animation-delay: 0.26s; }
.story-ed__process-step:nth-of-type(3) { animation-delay: 0.32s; }
.story-ed__process-step:nth-of-type(5) { animation-delay: 0.38s; }
.story-ed__process-step:nth-of-type(7) { animation-delay: 0.44s; }
@media (prefers-reduced-motion: reduce) {
  .story-ed__portrait, .story-ed__headline, .story-ed__body, .story-ed__process-step, .story-ed__photo--secondary { animation: none; }
}

@media (max-width: 900px) {
  .story-ed__grid { grid-template-columns: 1fr; }
  .story-ed__portrait { width: 100%; order: -1; }
  .story-ed__photo--secondary { order: 0; width: 100%; margin-top: clamp(20px, 4vw, 32px); }
  .story-ed__content { padding: clamp(28px, 6vw, 40px) 24px; max-width: none; }
}

/* ---- Pricing ---- */
/* Narrower + centered than the site's normal 1180px .container — the
   brief asked for the whole section to feel smaller/boutique rather
   than stretching edge-to-edge like the rest of the page. margin:0
   auto (inherited from .container) keeps it centered. Narrowed again,
   980px → 900px, plus the shorter image ratio and tighter internal
   spacing below, specifically so the whole section (heading through
   the closing CTA) fits within a normal laptop viewport at once
   instead of requiring a scroll to see all three cards. */
.prices-hero .container,
.prices-cards .container { max-width: 900px; }
/* Scoped override, not a change to the shared .section--paper/
   .section--paper-dim classes those rules come from (both are still
   used by every other editorial section on the page) — same single-
   class specificity, but this comes later in the file so it wins only
   here. A soft tint of the brand blue, not a saturated blue, so the
   dark ink heading/body text and the white cards on top both keep
   reading clearly. */
.prices-hero,
.prices-cards { background: #e9f2fc; }
/* Sized so the "Pricing · 04" eyebrow clears the sticky .site-header
   (84px tall) with genuine breathing room instead of being crowded
   against it or hidden underneath it on arrival — not the shared
   .section base rule's default (up to 96px), which left only ~12px of
   clearance, nor a fully compacted value, which hid the eyebrow
   behind the header entirely. */
.prices-hero { padding-top: clamp(96px, 8vw, 112px); padding-bottom: clamp(4px, 1vw, 8px); }
/* Heading area centered over the three cards below, per refinement
   brief: the label-num row, headline, and subtext all center as one
   balanced block. .prices-hero__head carries its own max-width (a bit
   narrower than the 900px section container) so the subtext line in
   particular doesn't stretch edge-to-edge; the headline can still
   exceed it slightly since display-editorial is meant to feel bold. */
.prices-hero__head {
  max-width: 640px;
  margin: 0 auto;
  text-align: center;
}
.prices-hero__head .label-num { justify-content: center; margin-bottom: 0.7em; }
.prices-hero__subtext {
  max-width: 480px;
  margin: clamp(6px, 1vw, 10px) auto 0;
  color: var(--ink-soft);
  font-size: clamp(0.95rem, 1.2vw, 1.05rem);
  line-height: 1.55;
}

/* Retuned for the new warm-paper background: flat, thin-bordered
   cards with a very soft shadow (not the old dark gradient + heavy
   shadow + big radius treatment, but not perfectly flat either —
   just enough lift to read as a card without looking like a SaaS
   pricing widget). Padding/radius are owned by the structural
   .price-card rule further below (see the comment there); this block
   only owns color/background/shadow. */
.prices-cards { padding-top: 0; }
.price-card {
  background: var(--white);
  border: 1px solid var(--line);
  box-shadow: 0 4px 20px rgba(16, 16, 18, 0.05);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.price-card:hover { border-color: var(--line-strong); transform: none; box-shadow: 0 8px 28px rgba(16, 16, 18, 0.08); }
.price-card__title { color: var(--ink); }
.price-card__tagline { color: var(--ink-soft); opacity: 0.75; }
.price-card__note { color: var(--ink-soft); opacity: 0.75; }
.price-card__note-label {
  display: block;
  margin-bottom: 3px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.68rem;
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--ink-soft);
  opacity: 1;
}

/* Shallow photo header, edge-to-edge across the top of the card —
   bleeds past the card's own padding (negative margin matching it
   exactly) so the photo reaches the card's edges while everything
   else stays inset. Same "honest placeholder" pattern as Celebrate's
   .celebrate__placeholder for the one card without a real photo yet. */
.price-card__media {
  position: relative;
  aspect-ratio: 3.4 / 1;
  overflow: hidden;
  background: var(--paper-dim);
}
.price-card__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The Sponsor Banners photo (a real banner, 1000x375 / ~2.7:1) is
   noticeably taller, proportionally, than this card's ~3.4:1 media
   box — object-fit: cover was cropping the "PROUD SPONSOR OF..."
   header and, worse, cropping the phone number clean off the bottom
   of the banner. fill stretches it to the box's exact width and
   height (a mild ~27% horizontal stretch) instead of cropping or
   leaving side padding — the box's height was confirmed correct, this
   just closes the width gap that contain was leaving. */
.price-card__media--fit img { object-fit: fill; }
.price-card__media--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: repeating-linear-gradient(135deg, var(--paper-dim), var(--paper-dim) 14px, #e2dccb 14px, #e2dccb 28px);
}
.price-card__media-tag {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.72rem;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--ink-soft);
  opacity: 0.6;
}

/* No wrap border/background here on purpose — a box around the table
   is what made this read as a spreadsheet dropped into the card
   instead of pricing rows that belong to it. The rows are separated
   only by hairline dividers (below), lighter than the site's normal
   --line so they stay in the background rather than drawing grid
   lines. */
.price-table th { color: var(--ink-soft); border-bottom: 1px solid rgba(16, 16, 18, 0.08); }
.price-table td { border-top: 1px solid rgba(16, 16, 18, 0.05); color: var(--ink); }
/* A pale blue wash (not the beige --paper-dim used elsewhere) so the
   "most popular" row reads as the brand-blue accent, not just a
   generic alternating-row tint. */
.price-table__popular td { background: rgba(28, 109, 208, 0.05); }
.price-table__popular td:last-child { color: var(--blue); }
/* A soft pale-blue pill with blue text, not a solid rectangle — reads
   as a quiet recommendation, not a button sitting inside a table. */
.price-table__popular-label { background: rgba(28, 109, 208, 0.12); color: var(--blue); }
/* Second stacked size in a popular/premium cell reads as a secondary
   option, not accidental leftover text. */
.price-table__size + .price-table__size { color: var(--ink-soft); font-size: 0.88em; }

/* ---- Materials & Options ---- */
/* Redesigned 2026-09-02 into a "material showroom" — a card grid per
   category instead of a long vertical spec list. Same header/body
   split as Pricing just above (.prices-hero/.prices-cards). */
/* Compacted 2026-09-03: the whole intro block (eyebrow, headline,
   subtext, recommendation strip) plus all five collapsed accordion
   rows now needs to fit inside one screen on a common laptop
   viewport (~768-900px tall) without scrolling — every value below
   was tightened toward that budget, not shrunk arbitrarily. Container
   also switched from Pricing's borrowed 900px back to the sitewide
   1180px grid (var(--max-width)) per that brief, so this section
   reads as wide as the rest of the page again. */
.materials-hero {
  padding-top: clamp(28px, 3.5vw, 40px);
  padding-bottom: clamp(6px, 1vw, 10px);
  /* Extremely light blue-gray gradient instead of a flat white
     rectangle — deliberately subtle, no texture/noise layered on top. */
  background: linear-gradient(180deg, #ffffff 0%, #f2f6fc 100%);
}
/* Eyebrow pulled in close to the headline below it (was the sitewide
   .label-num default of 1.4em, tuned for sections with much more
   room to spend). */
.materials-hero .label-num { margin-bottom: 0.5em; }
.materials-hero__subtext {
  max-width: 620px;
  margin: clamp(8px, 1vw, 10px) 0 0;
  color: var(--ink-soft);
  font-size: clamp(0.95rem, 1.2vw, 1.05rem);
  line-height: 1.5;
}
/* Compact horizontal recommendation strip — question+prompt on the
   left, "Get a Recommendation" on the right, one slim row instead of
   a stacked block, so it spends width instead of height. Stacks back
   to two lines only once the row genuinely can't fit (narrow/mobile). */
.materials-callout {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  margin: clamp(10px, 1.4vw, 14px) 0 0;
  padding: 10px 20px;
  max-width: 820px;
  border: 1px solid rgba(28, 109, 208, 0.12);
  border-left: 3px solid var(--blue);
  background: rgba(28, 109, 208, 0.04);
  border-radius: 0 8px 8px 0;
}
.materials-callout__text { margin: 0; color: var(--ink-soft); font-size: 0.86rem; line-height: 1.45; }
.materials-callout__text strong { color: var(--ink); font-weight: 700; }
/* Sits beside the text on wide rows (flex item, no longer stacked
   underneath it), so it needs its own top margin reset and a
   flex-shrink:0 to keep its arrow from wrapping awkwardly. */
.materials-callout__link {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 6px;
  margin-top: 0;
  color: var(--blue);
  font-weight: 700;
  font-size: 0.86rem;
  white-space: nowrap;
  text-decoration: none;
}
.materials-callout__link:hover { text-decoration: underline; }
.materials-callout__link:hover .materials-callout__arrow { transform: translateX(3px); }
.materials-callout__arrow { display: inline-block; transition: transform 0.2s ease; }
@media (prefers-reduced-motion: reduce) {
  .materials-callout__arrow { transition: none; }
}
@media (max-width: 640px) {
  .materials-callout { flex-direction: column; align-items: flex-start; }
  .materials-callout__link { margin-top: 8px; }
}

.materials-body { padding-top: clamp(4px, 1vw, 12px); padding-bottom: clamp(36px, 4vw, 48px); }

.materials { display: flex; flex-direction: column; }
/* One <details> per category. No per-row box/border-radius — a plain
   hairline-divided list, same "rows, not boxes" idea the Pricing
   tables above use, so five categories don't turn into five separate
   cards stacked on top of each other. */
.materials__category { border-bottom: 1px solid var(--line); }
.materials__category:first-child { border-top: 1px solid var(--line); }

/* Remove the browser's default disclosure marker (both the Firefox
   list-style route and Chrome/Safari's ::-webkit-details-marker) so
   the custom plus/minus toggle below is the only indicator. A left
   accent border (transparent -> blue) plus a very light blue wash is
   the "subtle hover/open state" the row needs to feel interactive
   without turning into a button. */
.materials__category-summary {
  display: flex;
  align-items: center;
  gap: 14px;
  /* Tuned so each collapsed row lands ~58-64px tall (was ~80px) — the
     main lever behind fitting all five rows in one screen. */
  padding: 17px 14px 17px 11px;
  border-left: 3px solid transparent;
  cursor: pointer;
  list-style: none;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}
.materials__category-summary::-webkit-details-marker { display: none; }
.materials__category-summary:hover { background: rgba(28, 109, 208, 0.035); border-left-color: var(--blue); }
.materials__category[open] > .materials__category-summary { background: rgba(28, 109, 208, 0.035); border-left-color: var(--blue); }
@media (prefers-reduced-motion: reduce) {
  .materials__category-summary { transition: none; }
}

/* Small understated line-icon per category — thin stroke, currentColor,
   matching the rest of the site's no-heavy-icon language. Tints blue
   on hover/open alongside the heading and toggle. */
.materials__category-icon {
  flex: none;
  width: 20px;
  height: 20px;
  color: var(--ink-soft);
  opacity: 0.65;
  transition: color 0.2s ease;
}
.materials__category-icon svg { width: 100%; height: 100%; display: block; }
.materials__category-summary:hover .materials__category-icon,
.materials__category[open] .materials__category-icon { color: var(--blue); opacity: 1; }

/* Name + descriptor sit on one baseline beside each other on desktop
   (the descriptor as a quiet aside next to the name, not a second
   full-width line) — this is what lets the row collapse to a single
   compact line instead of two. Drops back to a stacked column once a
   row genuinely doesn't have the width to spare. */
.materials__category-heading {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  column-gap: 10px;
  row-gap: 1px;
  flex: 1 1 auto;
  min-width: 0;
}
.materials__category-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1rem, 1.6vw, 1.15rem);
  color: var(--ink);
  transition: color 0.2s ease;
}
.materials__category-summary:hover .materials__category-name,
.materials__category[open] .materials__category-name { color: var(--blue); }
.materials__category-note {
  color: var(--ink-soft);
  opacity: 0.7;
  font-size: 0.8rem;
  font-style: italic;
}
@media (max-width: 640px) {
  .materials__category-heading { flex-direction: column; align-items: flex-start; gap: 2px; }
}
/* Hand-drawn plus (two lines forming a +) instead of a font icon or
   image — nothing to load, and it matches the thin-line, no-heavy-
   icon language the rest of the site uses. currentColor so it tints
   blue alongside the icon/heading on hover or when open. The whole
   glyph rotates 45deg when a row opens, which turns the + into an ×
   instead of collapsing to a minus. */
.materials__category-toggle {
  position: relative;
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--ink-soft);
  transition: color 0.2s ease, transform 0.25s cubic-bezier(.22, 1, .36, 1);
}
.materials__category-summary:hover .materials__category-toggle,
.materials__category[open] .materials__category-toggle { color: var(--blue); }
.materials__category[open] .materials__category-toggle { transform: rotate(45deg); }
.materials__category-toggle::before,
.materials__category-toggle::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  background: currentColor;
  transform: translate(-50%, -50%);
}
.materials__category-toggle::before { width: 14px; height: 2px; }
.materials__category-toggle::after { width: 2px; height: 14px; }
@media (prefers-reduced-motion: reduce) {
  .materials__category-toggle { transition: none; }
}

/* Expansion animation — handled by initMaterialsAccordion() in
   js/main.js, which measures the panel's real height and transitions
   it with plain `height`, not by fighting <details>'s own show/hide.
   (A pure-CSS grid-template-rows 0fr->1fr version was tried first: it
   failed because modern Chrome renders non-summary <details> children
   inside an internal ::details-content box that owns its own show/
   hide independent of any CSS applied to the children — overriding
   display on the child, even with !important, could not make it
   report a non-zero height while collapsed.) overflow: hidden here is
   just a safety net so the JS-driven transition never shows spillover
   content mid-animation; with JS disabled (or prefers-reduced-motion)
   nothing below applies any inline styles, and <details> falls back to
   its native instant, fully-accessible toggle exactly as it always
   has on this site. */
.materials__panel { overflow: hidden; }
/* Off-white/light blue-gray wash behind the expanded grid only — NOT
   the whole section — so it's clear the visitor has "entered" this
   one category. */
.materials__panel-inner {
  padding: clamp(16px, 2.5vw, 24px) 14px clamp(20px, 3vw, 28px);
  background: rgba(28, 109, 208, 0.035);
}

.materials__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
}
@media (max-width: 640px) {
  .materials__grid { grid-template-columns: 1fr; gap: 12px; }
}

/* Compact cards, not identical floating rectangles — a thin blue
   accent line at the top (the one restrained Sideline-blue detail
   per card) is the only color besides the "Best for" label and any
   Most Popular badge. */
.materials__card {
  position: relative;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 15px 16px 14px;
  overflow: hidden;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
}
.materials__card::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: var(--blue);
  opacity: 0.55;
}
.materials__card:hover {
  border-color: var(--line-strong);
  box-shadow: 0 8px 22px rgba(16, 16, 18, 0.07);
  transform: translateY(-1px);
}
@media (prefers-reduced-motion: reduce) {
  .materials__card { transition: none; }
  .materials__card:hover { transform: none; }
}
.materials__card-top { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 8px; }
/* Small textured swatch, not a fabricated product photo — one honest
   abstract pattern per real material family (vinyl/woven/mesh/paper/
   rigid/film/metal), reused across items that share that same actual
   substrate. Structured so a real material photo can drop in later
   (just an <img> inside .materials__swatch) without any markup
   changes. */
.materials__swatch {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 6px;
  border: 1px solid rgba(16, 16, 18, 0.08);
  background-color: #e7eaee;
}
.materials__swatch--vinyl { background-image: repeating-linear-gradient(135deg, #dfe4ea, #dfe4ea 2px, #cbd3dc 2px, #cbd3dc 4px); }
.materials__swatch--woven {
  background-image:
    repeating-linear-gradient(45deg, rgba(16, 16, 18, 0.08) 0 2px, transparent 2px 6px),
    repeating-linear-gradient(-45deg, rgba(16, 16, 18, 0.08) 0 2px, transparent 2px 6px);
  background-color: #ece6d9;
}
.materials__swatch--mesh { background-image: radial-gradient(circle, rgba(16, 16, 18, 0.28) 1px, transparent 1.4px); background-size: 6px 6px; background-color: #e4e9f0; }
.materials__swatch--paper { background-image: linear-gradient(160deg, #ffffff, #eef1f5); }
.materials__swatch--rigid { background-image: linear-gradient(135deg, #eef0f3, #d7dbe1); }
.materials__swatch--film { background-image: linear-gradient(120deg, #e9eff6 0%, #ffffff 45%, #dbe4ef 100%); }
.materials__swatch--metal { background-image: linear-gradient(135deg, #d2d9e0, #98a3b1 45%, #d2d9e0 100%); }
.materials__card-heading { flex: 1 1 auto; min-width: 0; }
.materials__card-name {
  margin: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 0.95rem;
  color: var(--ink);
}
/* Same subtle blue-pill language already used for "Most Popular" on
   the Pricing tables, reused here rather than inventing a new badge
   style — kept off every card except HD Banner. */
.materials__badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(28, 109, 208, 0.12);
  color: var(--blue);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.56rem;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  white-space: nowrap;
}
.materials__card-type {
  margin: 2px 0 0;
  color: var(--ink-soft);
  opacity: 0.75;
  font-size: 0.76rem;
  font-style: italic;
}
.materials__card-desc {
  margin: 0 0 10px;
  color: var(--ink-soft);
  font-size: 0.84rem;
  line-height: 1.55;
}
.materials__card-bestfor,
.materials__card-specs,
.materials__card-price {
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--ink-soft);
}
.materials__card-bestfor { margin-bottom: 4px; }
.materials__card-label {
  display: inline-block;
  margin-right: 6px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.6rem;
  letter-spacing: 0.9px;
  text-transform: uppercase;
}
/* "Best for" is the one line meant to catch the eye first when
   scanning a card, so its label goes brand blue; "Specs" stays
   understated gray with slightly wider tracking, a technical footnote
   rather than a second highlight. */
.materials__card-bestfor .materials__card-label { color: var(--blue); }
.materials__card-specs .materials__card-label { color: var(--ink-soft); opacity: 0.7; letter-spacing: 1.1px; }
.materials__card-price { margin-top: 4px; color: var(--blue); font-weight: 600; }
.materials__card-price .materials__card-label { color: var(--blue); opacity: 1; }

/* Closing guidance line for each open category — same "thin rule +
   centered blue link" language as .prices-cta above it, so the two
   sections keep reading as one system. */
.materials__panel-cta {
  margin: clamp(14px, 2vw, 18px) 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  text-align: center;
  font-size: 0.85rem;
  color: var(--ink-soft);
}
.materials__panel-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 4px;
  color: var(--blue);
  font-weight: 700;
  text-decoration: none;
}
.materials__panel-link:hover { color: var(--ink); }
.materials__panel-link:hover .materials__panel-arrow { transform: translateX(3px); }
.materials__panel-arrow { display: inline-block; transition: transform 0.2s ease; }
@media (prefers-reduced-motion: reduce) {
  .materials__panel-arrow { transition: none; }
}

/* ---- Trusted By ---- */
.trusted { padding-bottom: clamp(28px, 4vw, 44px); }
/* No max-width here on purpose (was 18ch, which stacked this into
   several short, narrow lines) — letting it run the container's full
   width reads as a long horizontal headline instead. */
.trusted__headline { font-size: clamp(2rem, 5vw, 3.6rem); max-width: none; }

.trusted__marquee {
  margin-top: clamp(28px, 5vw, 48px);
  overflow: hidden;
  border-top: 1px solid rgba(246, 242, 234, 0.18);
  border-bottom: 1px solid rgba(246, 242, 234, 0.18);
  padding: 22px 0;
  mask-image: linear-gradient(90deg, transparent, black 6%, black 94%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, black 6%, black 94%, transparent);
}
.trusted__track {
  display: flex;
  align-items: center;
  gap: 20px;
  width: max-content;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.1rem, 2.4vw, 1.7rem);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--paper);
  white-space: nowrap;
  animation: marquee-left 28s linear infinite;
}
.trusted__dot { color: var(--blue-light); opacity: 0.7; }
@keyframes marquee-left {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) { .trusted__track { animation: none; } }

/* ---- Final CTA ----
   Reuses .cta-band__rings from the original CTA-band component
   (still defined below) — same rings motif, now sitting on
   .section--ink instead of the old blue gradient. */
.final-cta { position: relative; overflow: hidden; text-align: center; }
.final-cta__headline {
  font-size: clamp(2.4rem, 6.5vw, 5.25rem);
  line-height: 1.05;
  margin-bottom: clamp(24px, 3vw, 36px);
}


/* ---------------- Pricing tables (Prices page) ---------------- */
/* Pulls this section up closer to the hero above (reduces the large
   empty gap) without touching the shared .section/.section--alt rules
   other pages rely on. */
/* Bottom padding trimmed from the base .section's clamp (up to 96px)
   — that plus Trusted By's own top spacing was adding up to a big
   dead gap between the cards and the next section. */
.prices-cards { padding-top: 0; padding-bottom: clamp(12px, 2vw, 20px); }

.price-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px;
}

/* Colors/background/shadow are set above in the "Pricing" block of the
   editorial redesign system (search .price-card there) — this rule
   only owns structural properties (radius/padding/transition) so the
   two don't fight over the same properties. Radius brought down again
   (8px → 6px) and padding tightened considerably — the brief wants
   these reading as compact information panels, not spacious cards.
   overflow: hidden so .price-card__media's top corners get clipped to
   match instead of poking out past the card's own rounded corners. */
/* display: flex + the note's margin-top: auto below is what keeps the
   Design/Artwork footer flush with the card's bottom edge and aligned
   across all three cards, even though Lawn Signs' table has one fewer
   column than the other two — without flex, a shorter card would just
   leave dead space below its own footer instead of the footer sitting
   at a shared baseline. */
.price-card {
  display: flex;
  flex-direction: column;
  border-radius: 8px;
  padding: 10px;
  overflow: hidden;
  transition: border-color 0.2s ease;
}
@media (prefers-reduced-motion: reduce) {
  .price-card { transition: none; }
}
/* Bleeds to the card's edges (negative margin matching the padding
   above exactly) so the photo reaches edge-to-edge across the top,
   independent of the card's own inset content. */
.price-card__media { margin: -10px -10px 8px; }
.price-card__title { margin-bottom: 3px; font-weight: 800; font-size: 1.1rem; }
.price-card__tagline { font-size: 0.76rem; line-height: 1.4; margin-bottom: 6px; }
/* A real footer, not leftover text under the table — set off from the
   pricing rows with both a hairline divider AND a very light fill
   (bled to the card's own left/right/bottom edges, same bleed
   technique as .price-card__media above, clipped to the card's
   rounded corners by its overflow: hidden) so Design/Artwork reads as
   its own distinct pricing component, not trailing fine print under
   the table. Neutral gray, not blue, so it doesn't compete with the
   "Most Popular" row's pale-blue treatment above it. Still pushed to
   the card's bottom edge via margin-top: auto (see the flex comment
   above) so it lines up across all three cards. */
.price-card__note {
  margin: auto -10px -10px;
  padding: 6px 10px 8px;
  background: rgba(16, 16, 18, 0.035);
  border-top: 1px solid rgba(16, 16, 18, 0.09);
  font-size: 0.71rem;
  line-height: 1.4;
}

/* No box around the table (see the color-block comment above) and no
   overflow-x — cell padding is trimmed low enough, and the "Most
   Popular" pill small enough, that Size/Option/Price fit on one line
   inside the card without ever needing to scroll or widen the card to
   compensate. white-space: normal on the cells is still the safety
   net so any remaining tight case wraps instead of overflowing. */
.price-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.78rem;
}
.price-table th,
.price-table td {
  padding: 4px 3px;
  text-align: left;
  white-space: normal;
}
.price-table th {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.62rem;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}
/* Prices read as a clean right-aligned column, not just another
   left-aligned line of text. */
.price-table th:last-child,
.price-table td:last-child {
  text-align: right;
}
/* The dollar amount is the strongest element in each row on purpose —
   bigger than the size/option cells beside it, not just bolder, so the
   eye lands on price first when scanning down a card. */
.price-table td:last-child {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.2em;
}
.price-table__popular td:last-child { font-weight: 800; font-size: 1.26em; }
/* "Starting at" stacks above the price instead of sitting inline
   before it — the price column is only ~45px wide at real desktop
   width, nowhere near enough room for "Starting at $44" on one line
   (measured ~96px needed), so a small muted label on its own line
   keeps the column from overflowing while still reading as a prefix. */
.price-table__starting {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.56rem;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--ink-soft);
  opacity: 0.75;
}
/* Small pale-blue pill (background/color set in the color block above)
   — text-sized, minimal padding, no solid rectangle, so it reads as a
   quiet note rather than a button sitting inside a table row. */
.price-table__popular-label {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 0.58rem;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Second size in a multi-size cell reads as its own line, not
   run-together stacked text. */
.price-table__size { display: block; line-height: 1.4; }

@media (max-width: 480px) {
  .price-table th, .price-table td { padding: 9px 4px; font-size: 0.76rem; }
}

/* CTA closing out the cards — a thin top rule ties it to the grid
   above (reads as one connected unit instead of the cards just
   trailing off into empty space), and the link itself carries real
   visual weight so it reads as the section's next step rather than a
   footnote. */
/* A short, polished informational callout, not a wall of legal fine
   print — short exact copy, a subtle brand-blue wash (the same wash
   formula already used for the "Most Popular" rows/pill, reused here
   rather than inventing a new tint) instead of neutral gray, and
   genuinely readable text size, since the previous longer/smaller/
   gray version read as skippable fine print. Centered; widened to
   820px (the ~852px the 900px-container card grid actually renders at
   once its own 24px side padding is subtracted) so it now reads as
   belonging to the same content width as the cards above it, not a
   noticeably narrower band. The Materials & Options link lives inside
   the same box, directly under the callout copy, so the two read as
   one component. */
.prices-disclaimer {
  max-width: 820px;
  margin: clamp(22px, 3vw, 30px) auto 0;
  padding: 14px 22px;
  background: rgba(28, 109, 208, 0.06);
  border: 1px solid rgba(28, 109, 208, 0.2);
  border-radius: 8px;
  text-align: center;
}
.prices-disclaimer__text {
  margin: 0;
  font-size: clamp(0.86rem, 1.05vw, 0.95rem);
  font-weight: 500;
  color: var(--ink-soft);
  line-height: 1.6;
}
.prices-disclaimer__label { font-weight: 800; color: var(--ink); }
.prices-disclaimer__strong { font-weight: 700; color: var(--ink); }
/* The one sentence customers most need to act on gets the site's
   brand-blue accent, not just bold — the same "reserve blue for the
   thing that matters" rule the pricing cards themselves follow (blue
   only on the actual Most Popular price, never decoratively). */
.prices-disclaimer__emphasis { font-weight: 700; color: var(--blue); }
.prices-disclaimer__link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 8px;
  color: var(--blue);
  font-weight: 600;
  font-size: 0.8rem;
  text-decoration: none;
}
.prices-disclaimer__link:hover { text-decoration: underline; }
.prices-disclaimer__link:hover .prices-disclaimer__arrow { transform: translateX(3px); }
.prices-disclaimer__arrow { display: inline-block; transition: transform 0.2s ease; }
@media (prefers-reduced-motion: reduce) {
  .prices-disclaimer__arrow { transition: none; }
}

.prices-cta {
  max-width: 640px;
  margin: clamp(10px, 1.5vw, 14px) auto 0;
  padding-top: clamp(6px, 1vw, 10px);
  border-top: 1px solid var(--line);
  text-align: center;
  font-size: 0.88rem;
  color: var(--ink-soft);
}
.prices-cta__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 4px;
  color: var(--blue);
  font-weight: 700;
  text-decoration: none;
}
.prices-cta__link:hover { color: var(--ink); }
.prices-cta__link:hover .prices-cta__arrow { transform: translateX(3px); }
.prices-cta__arrow { display: inline-block; transition: transform 0.2s ease; }
@media (prefers-reduced-motion: reduce) {
  .prices-cta__arrow { transition: none; }
}

/* =========================================================
   One-page Home restructure — Prices, Portfolio, Why Choose Us,
   and Contact are sections on this page instead of separate pages
   (see index.html). Portfolio/Prices/Why-Choose-Us/Our-Story now use
   the editorial redesign system (.work-*/.process*/.story-ed*/
   .price-*/.trusted*/.final-cta — search "EDITORIAL REDESIGN" near
   the Reveal-on-scroll block); .contact-*/.form-* below are reused
   as-is, kept on the original dark theme.
   ========================================================= */

/* Any section a nav link jumps to needs breathing room above it so
   the sticky header doesn't cover its heading on arrival. */
main section[id] { scroll-margin-top: 84px; }
/* #request-quote (.form-card) — every "Get a Quote" style CTA on the
   page jumps here now instead of #contact's top, so tapping one lands
   directly on the "Request a free quote" heading and form rather than
   the general phone/email/address card above it, which was easy to
   mistake for the whole section and miss the form entirely. */
#request-quote { scroll-margin-top: 84px; }

.btn--sm { padding: 10px 20px; font-size: 0.85rem; }

/* ---------------- Hero: eyebrow + giant wordmark ---------------- */
.hero__eyebrow {
  display: block;
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--blue-light);
  text-align: center;
  margin-bottom: 10px;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.5);
}

/* Bigger and heavier than the standard .brand--hero, on the extra-
   heavy display face — the single "poster moment" of the hero. Stacked
   as two independent lines (rather than one inline lockup) at a much
   larger, tighter-tracked size so it reads as one dominant kinetic-type
   beat instead of competing with the eyebrow/tagline around it. */
.brand--giant {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  font-family: var(--font-display-xl);
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(3.6rem, 13.5vw, 9.5rem);
  line-height: 0.86;
  letter-spacing: -1px;
}
.brand--giant .brand__main,
.brand--giant .brand__accent { display: block; }
.brand--giant .brand__accent { letter-spacing: -1px; margin-top: -0.06em; }

/* Floating quick-CTA nudge, bottom-right of the hero only. */
.hero__nudge {
  position: absolute;
  z-index: 2;
  right: 20px;
  bottom: 20px;
  max-width: 260px;
  background: rgba(24, 26, 29, 0.82);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-md);
  padding: 16px 18px;
  box-shadow: var(--shadow-md);
}
.hero__nudge p {
  margin: 0 0 12px;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--gray-700);
}
.hero__nudge-close {
  position: absolute;
  top: 6px;
  right: 8px;
  background: none;
  border: none;
  color: var(--gray-500);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  padding: 4px;
}
.hero__nudge-close:hover { color: var(--white); }
.hero__nudge.is-dismissed { display: none; }

@media (max-width: 640px) {
  .hero__nudge { left: 20px; right: 20px; max-width: none; bottom: 12px; }
}

/* Three concentric rings fading outward from center, CSS-only —
   reused by .final-cta below (used to also serve the removed
   .cta-band--big / .sign-off, both folded into .final-cta now). */
.cta-band__rings {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.cta-band__rings span {
  position: absolute;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.14);
}
.cta-band__rings span:nth-child(1) { width: 260px; height: 260px; }
.cta-band__rings span:nth-child(2) { width: 440px; height: 440px; border-color: rgba(255, 255, 255, 0.1); }
.cta-band__rings span:nth-child(3) { width: 640px; height: 640px; border-color: rgba(255, 255, 255, 0.06); }
.final-cta .container { position: relative; z-index: 1; }

/* ---------------- Contact section (merged from contact.html) ---------------- */
.contact-section .section-head { margin-bottom: 44px; }
