/* ============================================================
   Seawolf Cleaning Services — site.css
   Design system implementation of STYLE-GUIDE.md.
   Becomes style.css of the WordPress theme — see README-WORDPRESS.md.
   ============================================================ */

/* ---- Fonts (self-hosted; copy /assets/fonts/ into the theme and
   wp_enqueue_style this file — never a Google Fonts CDN link) ---- */
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/golos-text-700.ttf') format('truetype');
}
@font-face {
  font-family: 'Golos Text';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url('../fonts/golos-text-900.ttf') format('truetype');
}
/* Public Sans retired in favour of Poppins (see --font-body below); the old
   public-*.ttf files are left in assets/fonts/ unused rather than deleted, in case
   this gets reverted. */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/poppins-400.ttf') format('truetype');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/poppins-500.ttf') format('truetype');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/poppins-600.ttf') format('truetype');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../fonts/poppins-700.ttf') format('truetype');
}

/* ---- Reset ---- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }
img, svg { display: block; max-width: 100%; height: auto; }
ul, ol { list-style: none; }
a { color: inherit; text-decoration: none; }
button, input, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; }

/* ---- Tokens ---- */
:root {
  /* "White" sections lean cool toward the brand blue instead of a warm cream — a subtle
     tint derived from --navy via color-mix(), not a new hardcoded colour, so it still
     only ever authors the three approved hex values. */
  --bg:         color-mix(in srgb, #2587f7 3%, white);
  --bg-2:       color-mix(in srgb, #2587f7 6%, white);
  --surface:    #FFFFFF;
  --ink:        #0B1F2E;
  --ink-2:      #48545F;
  --line:       color-mix(in srgb, #2587f7 15%, white);
  --navy:       #2587f7;
  --navy-deep:  #163663;
  --navy-ink:   #FFFFFF; /* text on navy */
  --brass:      #88eb37;   /* the one accent */
  --brass-deep: color-mix(in srgb, #88eb37 78%, black);
  --sea-glass:  #88eb37;

  --font-display: 'Golos Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
  --font-body:    'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --container: 1600px;
  --gutter:    clamp(20px, 5vw, 64px);
  --rhythm:    clamp(64px, 8vw, 120px);
  --radius:    5px;

  --shadow-card: 0 1px 2px rgba(14,30,43,0.06);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

/* Intentionally no `@media (prefers-color-scheme: dark)` override: the site stays on
   the light palette regardless of the visitor's OS setting, mostly-white with navy/blue
   used only in deliberate accent bands (hero, CTA strips). There is no theme toggle in
   the UI, so auto-switching here previously meant any visitor with OS dark mode got a
   near-black site with no way to opt out. The dead :root[data-theme] blocks below are
   kept in case a manual toggle is added later, but nothing currently sets that attribute. */
:root[data-theme="dark"] {
  --bg:         #081521;
  --bg-2:       #0D1F2E;
  --surface:    #112B3E;
  --ink:        #EEF1F4;
  --ink-2:      #B9C4CC;
  --line:       #1E4362;
  --navy:       #2587f7;
  --navy-deep:  #163663;
  --brass:      #88eb37;
  --brass-deep: color-mix(in srgb, #88eb37 78%, black);
  --sea-glass:  #88eb37;
  --shadow-card: 0 1px 2px rgba(0,0,0,0.4);
}
:root[data-theme="light"] {
  --bg:         color-mix(in srgb, #2587f7 3%, white);
  --bg-2:       color-mix(in srgb, #2587f7 6%, white);
  --surface:    #FFFFFF;
  --ink:        #0B1F2E;
  --ink-2:      #48545F;
  --line:       color-mix(in srgb, #2587f7 15%, white);
  --navy:       #2587f7;
  --navy-deep:  #163663;
  --brass:      #88eb37;
  --brass-deep: color-mix(in srgb, #88eb37 78%, black);
  --sea-glass:  #88eb37;
  --shadow-card: 0 1px 2px rgba(14,30,43,0.06);
}

/* ---- Base ---- */
html {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--ink);
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  scroll-behavior: smooth;
  overflow-x: hidden;
  /* .site-header is sticky (71px desktop / 68px mobile) with no scroll-padding to
     compensate — any scrollIntoView (anchor links, :target, browser find-in-page,
     DevTools "scroll into view") tucks the target's top edge under the header
     instead of stopping below it. This is a global inset, not per-element scroll-margin. */
  scroll-padding-top: 90px;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
body { line-height: 1.65; }
:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }

.container { max-width: var(--container); margin-inline: auto; padding-inline: var(--gutter); }
/* Left-anchored narrow content column (headline/intro/prose blocks capped narrower than
   the site's normal 1600px container, for readability): margin-inline:0 alone only lines
   up with the full-width container's own content edge on viewports <=1600px, where that
   wider container's own auto-centering resolves to 0 anyway. On anything wider (a large
   desktop monitor), the full-width container still centers itself with a real margin, so
   a flat 0 here pulls the narrow column further left than the header/breadcrumb above it
   — this recreates that same centering offset instead of discarding it. */
.container--left { margin-inline: 0; margin-left: max(0px, calc((100vw - var(--container)) / 2)); }
.band { padding-block: var(--rhythm); border-top: 1px solid var(--line); }
.band:first-of-type { border-top: none; }
.band--bg2 { background: var(--bg-2); }
.band--navy { background: var(--navy); color: #fff; border-top-color: rgba(255,255,255,0.12); }
.band--navy .ink-2, .band--navy p { color: rgba(255,255,255,0.78); }
.band--navy-deep { background: var(--navy-deep); color: #fff; }
.band--navy-deep .ink-2, .band--navy-deep p { color: rgba(255,255,255,0.78); }
/* p.lede (element+class, same specificity as .band--navy p above) is defined further
   down and would otherwise win on source order alone, putting dark ink text back on a
   navy background — these need the extra class to actually out-specificity it. */
.band--navy .lede, .band--navy-deep .lede { color: rgba(255,255,255,0.9); }

/* ---- Type ---- */
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 800; line-height: 1.08; text-wrap: balance; color: var(--ink); }
.band--navy h1, .band--navy h2, .band--navy h3,
.band--navy-deep h1, .band--navy-deep h2, .band--navy-deep h3 { color: #fff; }

h1 { font-size: clamp(2.6rem, 1rem + 5vw, 4.6rem); font-weight: 900; letter-spacing: -0.01em; }
h2 { font-size: clamp(1.9rem, 1rem + 2.6vw, 2.75rem); font-weight: 800; }
h3 { font-size: 1.375rem; font-weight: 700; }
p { color: var(--ink-2); max-width: 65ch; }
p.lede { font-size: 1.125rem; line-height: 1.6; color: var(--ink); max-width: 58ch; }

.eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  font: 700 0.75rem/1 var(--font-body); text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--brass);
}
.eyebrow::before { content: ''; width: 18px; height: 1.5px; background: var(--brass); }

.tabular { font-variant-numeric: tabular-nums; }

/* ---- Buttons ---- */
/* Same interaction system as Custodia's public-site CTA button (app/styles/public-site.css
   .btn / .btn.primary): pill shape, lift + growing shadow on hover, press-down on active,
   a diagonal shine sweep across the primary variant on hover, focus-visible ring. Colour
   swapped to the brass accent; every shade here is derived from the three approved tokens
   via color-mix(), never a new literal hex. */
.btn {
  position: relative; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 13px 26px; border-radius: 999px; font: 700 0.9375rem var(--font-body);
  border: 1px solid transparent; max-width: 100%;
  box-shadow: 0 8px 20px rgba(11,31,46,0.1);
  transition: transform 180ms var(--ease-out), box-shadow 180ms var(--ease-out), background 180ms var(--ease-out), border-color 180ms var(--ease-out), color 180ms var(--ease-out);
  white-space: nowrap; cursor: pointer;
}
/* white-space:nowrap above is right for the site's normal short CTA labels (keeps them
   from breaking on an awkward word), but a longer label ("Ask us, we'll give you a
   straight answer") has no room to stay on one line once its column has shrunk to an
   actual phone's width — nowrap then just runs the text off the edge of the pill instead
   of wrapping it. Letting it wrap below 480px, where this only ever bites, keeps every
   other button's single-line look intact. */
@media (max-width: 480px) {
  .btn { white-space: normal; text-align: center; }
}
.btn svg { transition: transform 200ms ease; flex-shrink: 0; }
/* Lift kept minimal (was -2px) and the shadow growth toned down to match — still gives
   hover feedback without the pronounced "jump". */
.btn:hover { box-shadow: 0 10px 20px rgba(11,31,46,0.12); }
.btn:active { transform: translateY(0) scale(0.98); transition-duration: 80ms; }
.btn:focus-visible { outline: 3px solid color-mix(in srgb, var(--brass) 45%, transparent); outline-offset: 2px; }
.btn-primary {
  background: linear-gradient(180deg, color-mix(in srgb, var(--brass) 70%, white) 0%, var(--brass) 55%, var(--brass-deep) 100%);
  border-color: color-mix(in srgb, var(--brass) 80%, black);
  /* Light-green fill needs dark text for contrast, but full black reads harsh against a
     brand colour — navy-deep is dark enough and ties back to the palette instead of
     being a generic near-black. */
  color: var(--navy-deep);
  box-shadow: 0 6px 14px color-mix(in srgb, var(--brass) 22%, transparent), inset 0 1px 0 rgba(255,255,255,0.45);
}
.btn-primary::after {
  content: ''; position: absolute; top: 0; left: -70%; width: 45%; height: 100%;
  background: linear-gradient(105deg, transparent, rgba(255,255,255,0.5), transparent);
  transform: skewX(-20deg); transition: left 500ms ease; pointer-events: none;
}
.btn-primary:hover {
  background: linear-gradient(180deg, color-mix(in srgb, var(--brass) 45%, white) 0%, color-mix(in srgb, var(--brass) 80%, white) 55%, var(--brass) 100%);
  box-shadow: 0 9px 18px color-mix(in srgb, var(--brass) 27%, transparent), inset 0 1px 0 rgba(255,255,255,0.5);
}
.btn-primary:hover::after { left: 140%; }
.btn-primary:hover svg { transform: translateX(3px); }
.btn-secondary { background: transparent; color: var(--ink); border: 1.5px solid var(--ink); box-shadow: none; }
.band--navy .btn-secondary, .band--navy-deep .btn-secondary {
  color: rgba(255,255,255,0.92); text-shadow: 0 1px 2px rgba(0,0,0,0.2); border-color: rgba(255,255,255,0.55);
}
.btn-secondary:hover { border-color: var(--brass); color: var(--brass); }
.btn-text { padding: 0; color: var(--brass); font-weight: 700; border-bottom: 1.5px solid transparent; border-radius: 0; }
.btn-text:hover { border-bottom-color: var(--brass); }

/* ---- Plaque card ---- */
.plaque {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-card); padding: 28px; transition: transform 200ms var(--ease-out), border-color 200ms var(--ease-out);
}
.plaque:hover { transform: translateY(-2px); border-color: var(--brass); }
.plaque .icon { width: 40px; height: 40px; color: var(--brass); margin-bottom: 16px; }

/* Solid navy tiles sitting on a light section (not glass-on-dark): the section itself
   stays white/cream per the "mostly white, occasional blue" rule, and the blue lives in
   the cards, not the background. Numbers dropped (this is 7 unordered services, not a
   real sequence — STYLE-GUIDE.md's own anti-pattern rule) in favour of a proper tinted
   icon badge, which is what was actually missing before: real Lucide-drawn icons instead
   of hand-sketched ones, given real visual weight instead of floating bare on the card. */
.service-card {
  display: flex; flex-direction: column; height: 100%;
  background: var(--navy-deep);
  border: 1px solid var(--navy-deep); border-radius: 20px;
  box-shadow: var(--shadow-card);
  transition: background 220ms var(--ease-out), border-color 220ms var(--ease-out), transform 220ms var(--ease-out), box-shadow 220ms var(--ease-out);
}
.icon-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; border-radius: 14px; margin-bottom: 18px;
  background: color-mix(in srgb, var(--brass) 16%, transparent);
  transition: background 220ms var(--ease-out), transform 220ms var(--ease-out);
}
.icon-badge .icon { width: 26px; height: 26px; color: var(--brass); margin: 0; }
.service-card h3 { margin-top: 0; color: #fff; }
.service-card p { color: rgba(255,255,255,0.8); }
.service-card .go {
  margin-top: auto; padding-top: 20px; color: #fff; font-weight: 700;
  opacity: 0; transform: translateX(-4px); transition: opacity 200ms, transform 200ms;
}
.service-card:hover {
  background: var(--navy); border-color: var(--navy); transform: translateY(-4px);
  box-shadow: 0 20px 40px -16px rgba(3,10,18,0.4);
}
.service-card:hover .icon-badge { background: color-mix(in srgb, var(--brass) 30%, transparent); transform: scale(1.08) rotate(-4deg); }
.service-card:hover .go { opacity: 1; transform: none; }
.service-card:hover h3 { color: var(--brass); }

/* CTA tile folded into the services grid as its own 8th card (fills what would
   otherwise be an orphaned gap when 7 services sit in a 4-column grid), solid
   brass so it reads as the "do this next" action rather than an eighth service. */
.service-card--cta { background: var(--brass); border-color: var(--brass); justify-content: center; }
.service-card--cta:hover { background: var(--brass-deep); border-color: var(--brass-deep); transform: translateY(-4px); }
.service-card--cta .icon-badge { background: rgba(23,16,8,0.14); }
.service-card--cta .icon-badge .icon { color: #171008; }
.service-card--cta:hover .icon-badge { background: rgba(23,16,8,0.22); }
.service-card--cta h3, .service-card--cta p { color: #171008; }
.service-card--cta:hover h3 { color: #171008; }
.service-card--cta .go { color: #171008; opacity: 1; transform: none; }

/* ---- Badge (stamped plaque) ---- */
.badge {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); border-radius: 4px; padding: 6px 12px;
  font: 600 0.8125rem var(--font-body); color: var(--ink-2);
}
.badge .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--sea-glass); }

/* ---- Chart-line divider (signature move — used sparingly) ---- */
.chart-rule { height: 2px; background: var(--brass); width: 64px; margin-block: 32px; }

/* ---- Photo frame (brass corner-bracket, the one photo treatment) ---- */
.photo-frame { position: relative; }
.photo-frame::before, .photo-frame::after {
  content: ''; position: absolute; width: 30px; height: 30px; border: 3px solid var(--brass); pointer-events: none;
}
/* For the bracket's arc to actually trace the image's own 16px-radius corner rather than
   just sit near it, it needs to be CONCENTRIC with that corner, not the same radius —
   the bracket's box is offset 8px outside the image's edge (top/left:-8px below), so its
   own arc's centre point only lands on the image's arc centre if its radius is the
   image's radius PLUS that 8px offset (16+8=24), not a bare copy of 16px. Using 16px
   here (matching the image's radius but ignoring the offset) put the two arcs at
   different centres entirely, which is what read as the bracket not following the
   image's corner at all. */
.photo-frame::before { top: -8px; left: -8px; border-right: none; border-bottom: none; border-top-left-radius: 24px; }
.photo-frame::after  { bottom: -8px; right: -8px; border-left: none; border-top: none; border-bottom-right-radius: 24px; }
.photo-frame img, .photo-placeholder {
  width: 100%; aspect-ratio: 16 / 10; object-fit: cover; border-radius: 16px;
}
.photo-frame.ratio-4-3 img, .photo-frame.ratio-4-3 .photo-placeholder { aspect-ratio: 4 / 3; }
.photo-frame.ratio-1-1 img, .photo-frame.ratio-1-1 .photo-placeholder { aspect-ratio: 1 / 1; }
/* Fills the height of a stretched grid sibling instead of a fixed aspect ratio — for a
   grid-2 row where the text column's height varies with content (e.g. the logbook
   section), so the photo matches however tall that content actually is rather than being
   a fixed shape the text column has to accommodate. Needs the grid itself to actually
   stretch (CSS Grid's default align-items, so just not overridden to start/center). */
.photo-frame.ratio-fill { height: 100%; }
.photo-frame.ratio-fill img, .photo-frame.ratio-fill .photo-placeholder { height: 100%; aspect-ratio: auto; }
.photo-placeholder {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; text-align: center; padding: 28px;
  /* --surface sits too close to --bg-2 in dark mode (both near-black navy), which read
     as a flat black hole rather than a diagonal stripe — --line is calibrated as a
     divider tone so it stays visibly distinct from --bg-2 in both themes. */
  background: repeating-linear-gradient(135deg, var(--bg-2), var(--bg-2) 10px, var(--line) 10px, var(--line) 20px);
  border: 1px dashed var(--line); color: var(--ink-2); font: 500 0.75rem/1.6 var(--font-body);
}
.photo-placeholder::before {
  content: ''; width: 30px; height: 30px; flex-shrink: 0;
  background-color: var(--brass); opacity: 0.6;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 8h3l1.6-2.4A1 1 0 0 1 8.4 5h7.2a1 1 0 0 1 .8.4L17.8 8H20a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z'/%3E%3Ccircle cx='12' cy='13.5' r='3.5'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 8h3l1.6-2.4A1 1 0 0 1 8.4 5h7.2a1 1 0 0 1 .8.4L17.8 8H20a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z'/%3E%3Ccircle cx='12' cy='13.5' r='3.5'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* ---- Compass mark (used once per page) ---- */
.compass-mark { width: 56px; height: 56px; color: var(--brass); opacity: 0.9; }

/* ---- Grid helpers ---- */
.grid { display: grid; gap: 24px; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 900px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } }
/* Grid items default to min-width:auto, so a single wide-intrinsic-content child (a
   <select> sized by its longest <option>, a nowrap button label, an unbroken long word)
   can force its whole track — and with it the page — wider than the viewport, silently
   clipped by html's overflow-x:hidden below instead of wrapping. Confirmed live on
   contact.html at 320-375px (the service <select>'s longest option dragged the entire
   two-column form+copy grid, including the H1, off the right edge) and on the homepage
   areas section (the "Ask us..." button did the same). min-width:0 lets a track shrink to
   the space actually available and pushes the overflow-handling down to the child itself. */
.grid > * { min-width: 0; }

/* ---- Scroll reveal ---- */
.reveal { opacity: 0; transform: translateY(8px); transition: opacity 500ms var(--ease-out), transform 500ms var(--ease-out); }
.reveal.in { opacity: 1; transform: none; }

/* ---- Ribbon (scrolling marquee CTA bar) ---- */
.ribbon {
  background: var(--navy-deep); color: #fff; overflow: hidden; position: relative;
  border-bottom: 1px solid rgba(255,255,255,0.1);
}
.ribbon-track {
  display: flex; width: max-content; animation: ribbonScroll 140s linear infinite;
  padding-block: 8px; gap: 14px;
}
@media (prefers-reduced-motion: reduce) { .ribbon-track { animation: none; } }
.ribbon-item {
  display: flex; align-items: center; gap: 14px;
  font: 700 0.75rem var(--font-body); text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap;
}
.ribbon-item .dot-sep { width: 7px; height: 7px; border-radius: 50%; background: var(--brass); flex-shrink: 0; }
.ribbon-item a { color: #fff; }
.ribbon-item a:hover { color: var(--brass); }
@keyframes ribbonScroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* ---- Header / nav (glass) ---- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--navy) 80%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid rgba(255,255,255,0.14);
  overflow: visible;
}
.site-header .container { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding-block: 12px; }
/* flex-shrink:0 so the logo is never the thing that gives when the header is tight —
   the nav is what should collapse to the hamburger before the logo gets compressed. */
.brand { display: flex; align-items: center; gap: 10px; font: 800 1.125rem var(--font-display); color: #fff; flex-shrink: 0; }
.brand .compass-mark { width: 30px; height: 30px; color: var(--brass); }
/* Slim header bar, but the logo is allowed to overflow it (negative margin) so it reads
   bigger than the bar itself dictates, without dragging the whole header taller. */
.brand-logo { height: 58px; width: auto; margin-block: -9px; }
@media (max-width: 640px) { .brand-logo { height: 44px; margin-block: -4px; } }

/* Nav links and dropdown triggers as glass pills */
.primary-nav { display: flex; align-items: center; gap: 6px; }
.primary-nav a {
  font: 600 0.9375rem var(--font-body); color: rgba(255,255,255,0.9);
  padding: 9px 16px; border-radius: 999px; transition: background 160ms var(--ease-out), color 160ms var(--ease-out);
}
.primary-nav a:hover { color: #fff; background: rgba(255,255,255,0.14); }
.primary-nav .has-dropdown { position: relative; padding-block: 20px; margin-block: -20px; }
/* Duplicate of .nav-cta's CTA button, living inside .primary-nav so it can be relocated
   into the opened mobile panel (see the max-width:1200px block below) once the header
   bar itself is too narrow to hold the original. Hidden unconditionally here so it never
   renders as a second "Get a Free Quote" inline in the desktop nav — the mobile block
   is the only place that turns it back on, and only while .nav-open is present. */
.nav-mobile-cta { display: none; }
/* Chevron on dropdown trigger links (Services, Areas We Cover, Login): rotates open on
   hover/focus, purely CSS, no state to track. */
.chevron { display: inline-block; width: 14px; height: 14px; flex-shrink: 0; vertical-align: -2px; transition: transform 220ms var(--ease-out); margin-left: 2px; }
.has-dropdown:hover .chevron, .has-dropdown:focus-within .chevron { transform: rotate(180deg); }
.dropdown {
  position: absolute; top: 100%; left: 0; padding-top: 12px; min-width: 360px; display: none;
}
.has-dropdown:hover .dropdown, .has-dropdown:focus-within .dropdown { display: block; }
/* Deliberately dark navy always, not theme-toggled, it's a flyout of the navy header, so
   it should read as part of the header regardless of the page's own light/dark mode.
   Same navy as the ribbon bar above the header, for visual consistency. */
.dropdown-inner {
  background: var(--navy-deep); border: 1px solid rgba(255,255,255,0.1);
  border-radius: 14px; box-shadow: 0 20px 40px -16px rgba(3,10,18,0.5); padding: 14px;
  display: grid; grid-template-columns: repeat(2, minmax(170px, 1fr)); gap: 2px 6px;
  align-items: start;
}
.dropdown a {
  display: block; padding: 10px 14px; border-radius: 999px; color: rgba(255,255,255,0.88);
  font-weight: 600; font-size: 0.9375rem; transition: background 150ms, color 150ms;
}
.dropdown a:hover { background: rgba(255,255,255,0.1); color: var(--brass); }
.nav-cta { display: flex; align-items: center; gap: 12px; }
.site-header .btn {
  border-radius: 999px; padding-inline: 22px;
  box-shadow: 0 4px 16px -4px rgba(0,0,0,0.4);
}
.site-header .btn-primary:hover { box-shadow: 0 6px 22px -4px rgba(0,0,0,0.5); }
.nav-phone {
  display: none; align-items: center; gap: 8px; padding: 9px 18px; border-radius: 999px;
  border: 1.5px solid rgba(255,255,255,0.32); background: rgba(255,255,255,0.05);
  transition: border-color 160ms, background 160ms, transform 160ms;
}
.nav-phone:hover { background: rgba(255,255,255,0.14); border-color: rgba(255,255,255,0.6); transform: translateY(-1px); }
.nav-phone svg:not(.chevron) { width: 15px; height: 15px; flex-shrink: 0; }
/* Matches the primary-nav/hamburger breakpoint below (1200px) so the header doesn't pass
   through a mismatched state where the phone chip is back but the rest of the nav isn't. */
@media (min-width: 1200px) { .nav-phone { display: inline-flex; color: #fff; font-weight: 700; } }
/* A proper glass chip button (border, translucent fill, rounded corners) rather than a
   bare icon sitting directly in the header — plus the three spans morph into an X on open
   (keyed off aria-expanded, which site.js already sets, so no extra JS is needed). */
.nav-toggle {
  display: none; position: relative; width: 42px; height: 42px; flex-shrink: 0;
  background: rgba(255,255,255,0.14); border: 1.5px solid rgba(255,255,255,0.32);
  border-radius: 12px; cursor: pointer;
  transition: background 160ms var(--ease-out), border-color 160ms var(--ease-out);
}
.nav-toggle:hover { background: rgba(255,255,255,0.22); border-color: rgba(255,255,255,0.5); }
.nav-toggle span {
  position: absolute; left: 50%; top: 50%; width: 18px; height: 2px;
  background: #fff; border-radius: 99px; transform: translate(-50%, -50%);
  transition: transform 260ms var(--ease-out), opacity 200ms ease;
}
.nav-toggle span:nth-child(1) { margin-top: -6px; }
.nav-toggle span:nth-child(3) { margin-top: 6px; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { margin-top: 0; transform: translate(-50%, -50%) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { margin-top: 0; transform: translate(-50%, -50%) rotate(-45deg); }
@media (prefers-reduced-motion: reduce) { .nav-toggle span { transition: opacity 200ms ease; } }
@media (max-width: 1200px) {
  /* The full nav — logo, 5 links (2 with dropdowns), phone chip, login chip and CTA
     button — genuinely needs ~1200px+ to sit comfortably; 900px left a wide dead zone
     (roughly 900-1150px, e.g. tablets/small laptops) where it stayed in "desktop" mode
     but didn't fit, so links wrapped and the whole bar looked broken instead of
     switching to the hamburger. */
  .primary-nav { display: none; }
  .nav-toggle { display: inline-flex; align-items: center; justify-content: center; }
  /* Below this breakpoint, logo + CTA button + hamburger can't all fit one unwrapping row
     on real phone widths — the button's nowrap text and the toggle's fixed size both
     refuse to shrink, so the row overflowed and html's overflow-x:hidden silently clipped
     the hamburger off-screen entirely, making the menu unreachable. Bar shows logo +
     hamburger only now; the CTA moves into the opened panel instead (.nav-mobile-cta
     below), so it's still one tap away rather than gone. */
  .nav-cta > .btn-primary { display: none; }
  /* Hidden entirely until JS toggles .nav-open on click (site.js). Previously this class
     was toggled but had no visible-state rule at all, so the hamburger button did
     nothing. Submenus stay reachable the same way as desktop (:focus-within, i.e. tab or
     tap into a dropdown link), just laid out inline/static instead of as a flyout, since
     there's no hover on touch. */
  .primary-nav.nav-open {
    display: flex; flex-direction: column; align-items: stretch; gap: 2px;
    position: absolute; top: 100%; left: 0; right: 0;
    background: var(--navy-deep); padding: 10px clamp(20px, 5vw, 64px) 20px;
    border-bottom: 1px solid rgba(255,255,255,0.14);
    border-radius: 0 0 20px 20px;
    box-shadow: 0 20px 40px -16px rgba(3,10,18,0.5);
    /* calc(100svh - 100%) is meaningless against an auto-height element (100% resolves
       against its own indeterminate height); a fixed viewport-relative cap that leaves
       clear headroom for the header above it is what was actually intended. */
    max-height: calc(100svh - 90px); overflow-y: auto;
  }
  /* Dims the page behind the open panel instead of leaving it visible right up against
     the panel's own bottom edge (previously looked like a floating card cut off mid-page,
     worse still whenever the cookie banner was also showing beneath it). Anchored the same
     way as the panel itself (absolute against the sticky header, top:100%) rather than
     position:fixed, and rendered via ::before — inserted before .container in the box
     tree — so it paints underneath .primary-nav.nav-open, not on top of it. */
  .site-header::before {
    content: ''; position: absolute; top: 100%; left: 0; right: 0; height: 100vh;
    background: rgba(3,10,18,0.55);
    opacity: 0; pointer-events: none; transition: opacity 260ms var(--ease-out);
  }
  .site-header:has(.primary-nav.nav-open)::before { opacity: 1; }
  /* Every top-level entry (About/Blog/Contact, and Services/Areas as a whole block
     including whichever of their sublist is currently open) gets a hairline divider, so
     the panel reads as a rhythm of distinct sections instead of one undifferentiated
     column of 18+ same-weight links. The CTA is excluded — it gets its own treatment
     below, set apart as an action rather than another nav row. */
  .primary-nav.nav-open > a:not(.nav-mobile-cta), .primary-nav.nav-open > .has-dropdown {
    border-bottom: 1px solid rgba(255,255,255,0.08);
  }
  .primary-nav.nav-open > a, .primary-nav.nav-open .has-dropdown > a { padding: 14px 6px; }
  .primary-nav.nav-open .has-dropdown > a {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
  }
  .primary-nav.nav-open .nav-mobile-cta {
    display: inline-flex; width: 100%; justify-content: center;
    padding: 13px 26px; margin-top: 24px; border-top: 1px solid rgba(255,255,255,0.14); padding-top: 24px;
  }
  /* .primary-nav a:hover outranks .btn-primary:hover on specificity (element+class+pseudo
     beats class+pseudo), which would otherwise paint a translucent white overlay on hover
     instead of the button's own brass gradient. Restated explicitly here rather than left
     to lose the fight silently. */
  .primary-nav.nav-open .nav-mobile-cta:hover {
    color: var(--navy-deep);
    background: linear-gradient(180deg, color-mix(in srgb, var(--brass) 45%, white) 0%, color-mix(in srgb, var(--brass) 80%, white) 55%, var(--brass) 100%);
  }
  .primary-nav.nav-open .has-dropdown { padding-block: 0; margin-block: 0; }
  /* Services (7 links) and Areas We Cover (11 links) used to render permanently expanded
     inline, so opening the menu at all meant scrolling past 18 sub-links before reaching
     About/Blog/Contact. They're now closed by default and toggle open per-section (JS in
     site.js flips .mobile-open on the .has-dropdown wrapper instead of navigating, only
     while the menu is in this hamburger/accordion mode). The 0fr/1fr grid-template-rows
     trick animates height smoothly without measuring content in JS. */
  .primary-nav.nav-open .dropdown {
    position: static; padding-top: 0; min-width: 0;
    display: grid; grid-template-rows: 0fr;
    transition: grid-template-rows 320ms var(--ease-out);
  }
  .primary-nav.nav-open .has-dropdown.mobile-open .dropdown { grid-template-rows: 1fr; }
  .primary-nav.nav-open .has-dropdown.mobile-open > a .chevron { transform: rotate(180deg); }
  /* padding-block and border-left are fixed lengths, so the 0fr row above can only
     collapse to those values, never truly to 0 — grid-template-rows:0fr still leaves
     min-height:0's item tall enough to fit its own border-box, which was showing as a
     ~16px sliver (2px + 14px padding) under Services/Areas We Cover even fully closed.
     Confirmed via computed styles: grid-template-rows resolved to "16px", not "0px".
     Applying that padding/border only once .mobile-open is present removes the floor. */
  .primary-nav.nav-open .dropdown-inner {
    background: none; border: none; box-shadow: none; overflow: hidden; min-height: 0;
    padding: 0 0 0 14px; margin-left: 2px; border-left: 2px solid transparent;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 2px 12px;
  }
  .primary-nav.nav-open .has-dropdown.mobile-open .dropdown-inner {
    padding-top: 2px; padding-bottom: 14px; border-left-color: rgba(255,255,255,0.14);
  }
  /* Sub-links deliberately lighter (smaller, lower weight, dimmer) than the bold
     top-level items, so Services/Areas We Cover/About/Blog/Contact stay the visually
     dominant scan path and the expanded sub-items read as detail, not equal-weight nav. */
  .primary-nav.nav-open .dropdown a {
    padding: 9px 10px; font-size: 0.875rem; font-weight: 500; color: rgba(255,255,255,0.72);
  }
  .primary-nav.nav-open .dropdown a:hover, .primary-nav.nav-open .dropdown a:focus-visible {
    color: #fff; background: rgba(255,255,255,0.08);
  }
}

/* ---- Footer ---- */
.site-footer { background: var(--navy-deep); color: rgba(255,255,255,0.8); }
/* The global `p { color: var(--ink-2) }` rule (a dark grey meant for light backgrounds)
   targets every <p> directly, which wins over an ancestor's inherited colour regardless
   of specificity — so footer paragraphs were rendering in near-invisible dark grey on
   navy without this explicit override. */
.site-footer p { color: rgba(255,255,255,0.72); }
.site-footer .container { padding-block: var(--rhythm); }
.footer-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 40px; }
@media (max-width: 900px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer-grid { grid-template-columns: 1fr; } }
.site-footer h4 { color: #fff; font-size: 0.8125rem; text-transform: uppercase; letter-spacing: 0.06em; font-family: var(--font-body); font-weight: 700; margin-bottom: 14px; }
.site-footer ul { display: flex; flex-direction: column; gap: 10px; }
.site-footer a:hover { color: var(--brass); }
.footer-bottom { border-top: 1px solid rgba(255,255,255,0.12); margin-top: 48px; padding-top: 24px; display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px; font-size: 0.8125rem; color: rgba(255,255,255,0.55); }
.footer-social { display: flex; align-items: center; gap: 16px; }
.footer-social a { color: rgba(255,255,255,0.75); display: inline-flex; }
.footer-social a:hover { color: var(--brass); }
.footer-social svg { width: 19px; height: 19px; }

/* ---- Wolf watermark (decorative background mark) ---- */
.wolf-watermark {
  /* Square source image, so width and height track together. Capped well under a typical
     CTA band's rendered height (~510px) so a centred watermark always has clear padding
     above and below instead of colliding with the section's own overflow:hidden edge. */
  position: absolute; pointer-events: none; opacity: 0.05; z-index: 0;
  width: clamp(240px, 26vw, 380px); height: auto;
}

/* ---- Full-viewport hero: at least one screen tall at any size (min-height, not a fixed
   height — a fixed height plus overflow:hidden could clip the wave, the text, or both
   whenever content is taller than a short viewport, which is worse than occasionally
   needing a small scroll). Wave sits at the very bottom of the actual content, so it is
   always reachable, on very short viewports that means a touch of scroll, never hidden. */
.hero-full {
  min-height: 92svh;
  display: flex;
  align-items: center;
  position: relative;
  /* z-index:0 (not the default auto) so this establishes its own stacking context —
     without it, .hero-slideshow's z-index:-1 doesn't necessarily nest locally beneath
     just this section's own background; it can resolve against a stacking context
     further up the tree instead, which is what was actually happening (slideshow
     rendering, but painted behind something else entirely, invisible). */
  z-index: 0;
  overflow: hidden;
  /* Pulled up behind .site-header (sticky, translucent + backdrop-blur) so the slideshow
     shows through it at the very top of the page, rather than the hero starting flush
     below the header's own box. padding-top adds back exactly what margin-top removes,
     so the headline/buttons land in the same visual spot as before — only the background
     actually extends further up. 71px/68px match the header's own measured height at
     each breakpoint (see the comment on html's scroll-padding note above). */
  margin-top: -71px;
  padding-block: calc(71px + clamp(16px, 2vw, 28px)) clamp(110px, 13vw, 190px);
}
@media (max-width: 640px) {
  .hero-full { margin-top: -68px; padding-top: calc(68px + clamp(16px, 2vw, 28px)); }
}
/* Crossfading background slideshow, reusing real photos already on the site (markup in
   index.html) instead of one static shot. Pure CSS: one shared @keyframes, each slide
   staggered onto its own slice of the same cycle via a negative animation-delay — the
   standard technique for an N-slide crossfade with a single keyframe definition. Sits at
   z-index:-1, one layer behind the scrim below (z-index:0), so it occupies exactly the
   layer the old flat background image used to. --navy-deep fallback on .hero-photo covers
   the instant before the first slide has painted (and CSS/image-load failure generally).
   The dark scrim on top (a gradient, painted via ::before so text stays readable over the
   left side) is a deliberate permanent choice, not theme-dependent — this used to switch
   to a light cream scrim whenever the visitor's OS was in light mode, which is why the
   hero and this dark-scrim look weren't always the same thing; it's now always the dark
   version regardless of OS setting. */
.hero-photo { background: var(--navy-deep); }
.hero-slideshow { position: absolute; inset: 0; z-index: -1; }
.hero-slide {
  position: absolute; inset: 0;
  background-size: cover; background-position: center 30%;
  opacity: 0;
  animation: heroSlideFade 36s ease-in-out infinite;
}
/* Positive delays, not negative — a positive delay holds a slide at its 0% keyframe
   (opacity:0) until its turn arrives, which is what staggers them into sequential order.
   (Negative delay does the opposite: it fast-forwards a slide as if already partway
   through at page load, which put later slides on screen too early.) */
.hero-slide:nth-child(1) { animation-delay: 0s; }
.hero-slide:nth-child(2) { animation-delay: 6s; }
.hero-slide:nth-child(3) { animation-delay: 12s; }
.hero-slide:nth-child(4) { animation-delay: 18s; }
.hero-slide:nth-child(5) { animation-delay: 24s; }
.hero-slide:nth-child(6) { animation-delay: 30s; }
/* Each slide's own 36s timeline: fade in (2s), hold, then fade out (2s) DURING the same
   2s window the next slide is fading in (its delay is exactly one 6s slot later, so its
   own 0%-to-5.56% fade-in window lands in real time exactly on top of this slide's
   16.67%-to-22.22% fade-out window) — a true cross-dissolve where both images overlap on
   screen throughout the handoff, rather than this slide finishing its fade-out before the
   next one starts fading in, which left a moment where neither was meaningfully visible
   and the navy background behind them showed through. */
@keyframes heroSlideFade {
  0%     { opacity: 0; }
  5.56%  { opacity: 1; }
  16.67% { opacity: 1; }
  22.22% { opacity: 0; }
  100%   { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-slide { animation: none; opacity: 0; }
  .hero-slide:first-child { opacity: 1; }
}
.hero-photo::before {
  content: ''; position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(90deg, rgba(8,21,33,0.96) 0%, rgba(8,21,33,0.92) 38%, rgba(8,21,33,0.5) 60%, rgba(8,21,33,0.05) 82%);
}
@media (max-width: 800px) {
  /* Stack: on narrow screens the gradient goes top-to-bottom instead of side-to-side,
     since there's no room for text-left/photo-right. */
  .hero-photo::before {
    background: linear-gradient(180deg, rgba(8,21,33,0.96) 0%, rgba(8,21,33,0.92) 45%, rgba(8,21,33,0.5) 65%, rgba(8,21,33,0.1) 100%);
  }
}
/* Hero copy runs smaller than the site's default h1/lede scale on purpose, so the whole
   hero, headline, copy, buttons, trust strip and the wave, realistically fits in one
   screen on ordinary laptop/desktop viewports, not just after a scroll. */
.hero-full h1 { font-size: clamp(2.1rem, 1rem + 3.4vw, 3.4rem); }
.hero-full .lede { font-size: 1rem; line-height: 1.55; }
.hero-full .trust-strip { font-size: 0.75rem; }
/* Hardcoded, not var(--ink)-based: light text, permanently, to match the dark scrim above. */
.hero-full h1, .hero-full .lede { color: #EEF1F4; }
.hero-full .trust-strip { color: #B9C4CC; }
/* The hero isn't wrapped in .band--navy/.band--navy-deep (it's its own photo+scrim
   component), so the secondary "Call" button doesn't automatically pick up the
   light-text-on-dark-background treatment those classes give it elsewhere — needs its
   own override here or it renders with dark ink text/border, illegible on this scrim. */
.hero-full .btn-secondary { color: rgba(255,255,255,0.92); text-shadow: 0 1px 2px rgba(0,0,0,0.25); border-color: rgba(255,255,255,0.55); }
.hero-full .btn-secondary:hover { border-color: var(--brass); color: var(--brass); text-shadow: none; }
/* On short viewports (laptops in a small window, embedded preview panes), shrink further
   so the whole hero still fits in one screen without clipping anything. */
@media (max-height: 760px) {
  .hero-full { padding-block: clamp(16px, 2vw, 28px) clamp(90px, 10vw, 140px); }
  .hero-full h1 { font-size: clamp(1.75rem, 1rem + 2.6vw, 2.6rem); }
  .hero-full .lede { font-size: 0.9375rem; }
  .hero-full .wave-divider svg { height: clamp(80px, 10vw, 140px); }
}

/* ---- Wave divider: layered (3 depths, like a real shoreline), genuinely animated (SMIL,
   see index.html), fully opaque distinct blues at every depth (no transparency-over-light-
   background, which is what was reading as grey), zero-gap hand-off into the next (navy)
   section since the front layer is painted the exact navy of that section's background. ---- */
.wave-divider { position: absolute; left: 0; right: 0; bottom: 0; z-index: 1; line-height: 0; pointer-events: none; }
.wave-divider svg { width: 100%; height: clamp(100px, 13vw, 190px); display: block; }
/* Layer 1 (front, fastest, 17s): fully opaque, this is the reference layer and also the
   one that must stay solid for the seamless hand-off into the section below.
   Layer 2 (mid, slower, 21s): ~15% more transparent than layer 1 (within the 10-20% ask).
   Layer 3 (back, slowest, 26s): ~30% more transparent than layer 1 (within the 20-40% ask). */
.wave-divider .layer-back { fill: color-mix(in srgb, var(--navy) 55%, white); opacity: 0.7; }
.wave-divider .layer-mid { fill: color-mix(in srgb, var(--navy) 80%, white); opacity: 0.85; }
.wave-divider .layer-front { fill: var(--navy); opacity: 1; }
/* The overlap promised above never actually landed as code, and couldn't have worked from
   this side anyway: .hero-full has overflow:hidden, which clips off any negative bottom
   offset on its own child before it could reach past the section boundary. Pulling the
   next section up over the seam instead lives outside .hero-full entirely, so nothing
   clips it — and since .band--navy's background is the exact same --navy as the front
   wave layer, a few px of overlap is invisible rather than a visible double-edge. This is
   what was producing the hairline/gap under the wave at various viewport widths: the
   wave's height is a vw-based clamp(), so at most widths it lands on a fractional pixel
   that browsers round differently for layout vs. rasterizing the SVG, leaving a 1-device-
   pixel sliver of whatever sits behind the wave (the hero photo/scrim) showing through. */
.hero-full + .band { margin-top: -3px; }

/* ---- Stat counters ---- */
.stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; }
@media (max-width: 800px) { .stat-row { grid-template-columns: repeat(2, 1fr); } }
.stat-item { text-align: center; min-width: 0; }
.stat-num { font: 900 clamp(4rem, 8vw, 5.5rem) var(--font-display); color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,0.16); }
/* clamp()'s 4rem floor is sized for the 800px 2-column handoff (8vw == 4rem right at
   800px) and never actually shrinks further below it — every phone width was rendering
   "2023"/"100%" at a fixed 64px, wider than a half-width column has room for. Confirmed
   live: the counted-up row measured a fixed ~384px wide from 320px through 375px
   viewports (min-width:0 above stops it forcing the page wider; this stops it forcing
   the number's own box wider than that). */
@media (max-width: 560px) {
  .stat-num { font-size: clamp(2.25rem, 13vw, 3.5rem); }
}
.stat-label { font: 700 0.8125rem var(--font-body); text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255,255,255,0.85); margin-top: 6px; text-shadow: 0 1px 3px rgba(0,0,0,0.14); }

/* ---- Brand mark (real logo, not a drawn substitute) ---- */
.brand-badge { width: 40px; height: 40px; }

/* ---- "Powered by" plate: white chip so the Custodia logo (dark wordmark, transparent
   background) stays legible over the hero photo in both light and dark theme. ---- */
.powered-by-plate {
  display: inline-flex; align-items: center; gap: 10px;
  background: rgba(255,255,255,0.94);
  border-radius: 10px;
  padding: 8px 14px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.14);
  text-decoration: none;
  transition: box-shadow 0.15s, transform 0.15s;
}
.powered-by-plate:hover { box-shadow: 0 4px 14px rgba(0,0,0,0.2); transform: translateY(-1px); }
.powered-by-label {
  font: 700 0.6875rem var(--font-body);
  text-transform: uppercase; letter-spacing: 0.05em;
  color: #6B7280;
}
.powered-by-logo { height: 32px; width: auto; display: block; }

/* ---- Trust strip (inline manifest-style markers, replaces badge pills in hero) ---- */
.trust-strip { display: flex; flex-wrap: wrap; align-items: center; row-gap: 12px; column-gap: 0; font: 700 0.8125rem var(--font-body); text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-2); }
.trust-strip span { display: flex; align-items: center; gap: 8px; padding-right: 20px; margin-right: 20px; border-right: 1.5px solid var(--line); }
.trust-strip span:last-child { border-right: none; margin-right: 0; padding-right: 0; }
@media (max-width: 480px) {
  /* Below ~480px the three items wrap one-per-line often enough that the vertical
     divider reads as a stray mark rather than a separator between neighbours, so drop
     it and rely on row-gap alone for spacing at that width. */
  .trust-strip span { border-right: none; padding-right: 0; margin-right: 0; }
}
.trust-strip .tick {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 20px; height: 20px; border-radius: 50%; background: var(--brass);
}
.trust-strip .tick svg { width: 11px; height: 11px; }

/* ---- Manifest list (services / areas) ---- */
.manifest { border-top: 1px solid var(--line); }
.manifest-row {
  display: grid; grid-template-columns: 64px 1fr auto; gap: 20px; align-items: start;
  padding: 26px 0; border-bottom: 1px solid var(--line);
  text-decoration: none; color: inherit;
}
.manifest-row .num { font: 800 1.5rem var(--font-display); color: var(--brass); }
.manifest-row h3 { margin-bottom: 6px; }
.manifest-row .go { align-self: center; color: var(--navy); font-weight: 700; opacity: 0; transform: translateX(-4px); transition: opacity 200ms, transform 200ms; }
.manifest-row:hover .go { opacity: 1; transform: none; }
.manifest-row:hover h3 { color: var(--brass-deep); }
@media (max-width: 640px) {
  .manifest-row { grid-template-columns: 40px 1fr; }
  .manifest-row .go { display: none; }
}

/* ---- Voyage scroll timeline (how it works) ---- */
/* Six-stage process, plate cards in a single horizontal row. On desktop this section
   pins to the viewport while the visitor scrolls, translating .voyage-track sideways
   (see initVoyageScroll() in site.js) — .voyage-pin-wrapper's height is set in JS to
   exactly the extra scroll distance the horizontal travel needs, so the page resumes
   normal scrolling the instant the last card arrives, no scroll-jacking/preventDefault.
   Mobile and reduced-motion get a plain native horizontally-swipeable row instead —
   see the max-width:900px block. */
.voyage-scroll-section { background: var(--bg-2); border-top: 1px solid var(--line); }
.voyage-pin-wrapper { position: relative; }
.voyage-pin-inner {
  position: sticky; top: 0; min-height: 100vh;
  display: flex; flex-direction: column; justify-content: center;
  padding-block: var(--rhythm);
}
/* container-type establishes .voyage-viewport as the reference for the cqw unit below —
   deliberately not a plain % on --voyage-edge-pad, because a % custom property re-resolves
   independently everywhere it's var()'d in, against THAT usage's own containing block.
   Used in .voyage-track's padding-inline it correctly meant .voyage-viewport's width, but
   the exact same variable reused in .voyage-line's left/right (a child of .voyage-track)
   resolved against .voyage-track's own width:max-content instead — thousands of pixels
   too wide — collapsing the line to zero width. cqw always means "this queried
   container's width" regardless of which element/property references it, so both usages
   agree. */
/* padding-block gives the dots' pulse ring (a few px larger than the dot itself) genuine
   box space to expand into before it reaches this element's own clip boundary. Splitting
   into overflow-x:hidden/overflow-y:visible looks like the more surgical fix but doesn't
   work — per the CSS Overflow spec, pairing a non-visible overflow-x with visible
   overflow-y forces the visible one to compute as auto instead, which still clips. */
.voyage-viewport { width: 100%; padding-block: 24px; overflow: hidden; container-type: inline-size; }
.voyage-track {
  --voyage-stop-w: 320px;
  --voyage-edge-pad: calc((100cqw - var(--voyage-stop-w)) / 2);
  position: relative; display: flex; gap: 32px;
  width: max-content; padding-inline: var(--voyage-edge-pad);
  will-change: transform;
}
.voyage-stop { flex: 0 0 var(--voyage-stop-w); display: flex; flex-direction: column; align-items: center; }

/* The line spans exactly from the first dot's centre to the last dot's centre — inset by
   half a stop's width on each side — rather than edge-to-edge of the track, which is what
   previously left a bare line trailing past the final dot with nothing at the end of it. */
.voyage-line {
  position: absolute; top: 10px;
  left: calc(var(--voyage-edge-pad) + var(--voyage-stop-w) / 2);
  right: calc(var(--voyage-edge-pad) + var(--voyage-stop-w) / 2);
  height: 2px; background: var(--brass); opacity: 0.35;
  transform-origin: left center; transform: scaleX(0);
  /* No transition here on purpose — its scaleX is set every scroll frame in
     updateVoyageScroll() (site.js), directly from the same progress value that drives
     the track's translateX, so the line's drawn length and the cards' horizontal
     position never fall out of sync with each other or lag behind the scroll input. */
}

/* Dot is a 20px box, border-box like everything else on this site (site.css's global
   `*{box-sizing:border-box}` reset) — the 3px border sits inside that 20px, not added
   outside it, leaving a 14px visible brass circle. Its vertical centre (10px) is what
   .voyage-line's own top:10px is matched against above; horizontally it's centred by
   .voyage-stop's flex column, the same centre the line's left/right calc lands on. */
.voyage-dot {
  width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0;
  background: var(--brass); border: 3px solid var(--bg-2);
  position: relative; z-index: 2; margin-bottom: 28px; opacity: 1; scale: 1;
  transition: scale 200ms var(--ease-out), opacity 320ms var(--ease-out);
}
/* Stem connecting the dot down to its plate, filling exactly the 28px margin-bottom
   gap above so it visually reads as one continuous line from the route down to the card. */
.voyage-dot::before {
  content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  width: 2px; height: 28px; background: var(--brass); opacity: 0.3;
}
.voyage-dot::after {
  content: ''; position: absolute; inset: -4px; border-radius: 50%; border: 2px solid var(--brass);
  opacity: 0; pointer-events: none;
}
.voyage-track.in .voyage-dot::after { animation: voyagePulse 2600ms var(--ease-out) infinite var(--pd, 0ms); }
@keyframes voyagePulse {
  0% { transform: scale(0.7); opacity: 0.55; }
  100% { transform: scale(2); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .voyage-dot::after { animation: none !important; } }

.voyage-plate {
  width: 100%; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-card); padding: 24px; opacity: 1; translate: 0 0;
  /* transform is reserved for the hover lift below; the reveal-on-scroll offset uses the
     standalone translate property instead so the two never fight over the same property. */
  transition: transform 200ms var(--ease-out), border-color 200ms var(--ease-out), opacity 380ms var(--ease-out) 60ms, translate 380ms var(--ease-out) 60ms;
}
.voyage-plate:hover { transform: translateY(-2px); border-color: var(--brass); }
.voyage-num {
  font: 800 1.5rem var(--font-display); color: var(--brass); font-variant-numeric: tabular-nums;
}

/* Progressive reveal (desktop/pinned mode only — see updateVoyageScroll() in site.js):
   only stop 1 is visible the moment the section is reached; each later stop (marked
   .voyage-stop--hideable in the markup — NOT :not(:first-child), since .voyage-line is
   itself .voyage-track's actual first child, which quietly made :first-child match
   nothing and hid every stop including the first) pops in with a quick, eased
   opacity+motion transition once the line (scaleX'd every scroll frame, above) reaches
   it, toggled by .is-revealed. Deliberately reversible both ways rather than "stays
   revealed forever" — scrolling back up retracts the line and re-hides later stops in
   step with it, which is what keeps the line's drawn length and each card's presence
   always describing the same, consistent state instead of drifting apart. */
@media (min-width: 901px) {
  .voyage-stop--hideable .voyage-dot { opacity: 0; scale: 0.3; }
  .voyage-stop--hideable .voyage-plate { opacity: 0; translate: 0 18px; }
  .voyage-stop--hideable.is-revealed .voyage-dot { opacity: 1; scale: 1; }
  .voyage-stop--hideable.is-revealed .voyage-plate { opacity: 1; translate: 0 0; }
  .voyage-track .voyage-dot:hover { scale: 1.4; }
}

@media (max-width: 900px) {
  /* No pin, no JS transform (initVoyageScroll bails out under this breakpoint) — a plain
     native horizontally-scrollable row instead, since scroll-jacking a vertical gesture
     on a touchscreen is exactly the wrong move on mobile. */
  .voyage-pin-wrapper { height: auto !important; }
  .voyage-pin-inner { position: static; min-height: 0; overflow: visible; }
  .voyage-viewport {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity; padding-bottom: 14px;
  }
  .voyage-track { transform: none !important; padding-inline: 20px; }
  .voyage-stop { --voyage-stop-w: 78vw; scroll-snap-align: start; }
  .voyage-line { display: none; }
}

/* ---- Logbook (why us) ---- */
/* Line grows downward, then each entry (with its dot) lights up one by one as it's
   scrolled into view, staggered via the --sd custom property set per entry.
   .logbook's own left padding used to be part of this calculation, but padding on the
   containing block does nothing for an absolutely positioned child's `left` origin (only
   normal-flow children like .logbook-entry respect it) — so the line's left:0 and the
   dot's left:-3px (measured from two different effective baselines once you factor that
   in) were 6px apart instead of sharing a centre. Padding removed; both the line and the
   dot now measure from the same left:0 baseline, sized so a 2px line centres exactly
   under a 12px dot. */
.logbook { position: relative; }
.logbook::before {
  content: ''; position: absolute; left: 5px; top: 6px; width: 2px; height: 0;
  background: var(--brass);
  transition: height 900ms var(--ease-out);
}
/* --logbook-line-h is set precisely by site.js from the last entry's actual measured dot
   position — a fallback of calc(100% - 12px) covers the instant before that JS runs (or
   if it's unavailable), but that heuristic is exactly what let the line overshoot past
   the last dot and run on through that entry's own body paragraph: "100% of the
   container" includes the trailing paragraph text below the last dot, which has nothing
   to do with where the line should actually stop. */
.logbook.in::before { height: var(--logbook-line-h, calc(100% - 12px)); }
.logbook-entry {
  padding: 0 0 0 28px; margin-bottom: 30px; position: relative;
  opacity: 0; transform: translateY(16px);
  transition: opacity 500ms var(--ease-out) var(--sd, 0ms), transform 500ms var(--ease-out) var(--sd, 0ms);
}
.logbook-entry.in { opacity: 1; transform: none; }
/* Dot centred on the heading's own line box (h3 line-height 1.08 at 1.375rem ≈ 23.75px
   tall) rather than a guessed fixed offset. A soft ring behind the solid dot gives it a
   bit more presence on the line, like a lit waypoint rather than a bare mark. */
.logbook-entry::before {
  content: ''; position: absolute; left: 0; top: calc((1.375rem * 1.08 - 12px) / 2);
  width: 12px; height: 12px; border-radius: 50%; background: var(--brass);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brass) 16%, transparent);
  transform: scale(0); transition: transform 400ms var(--ease-out) var(--sd, 0ms);
}
.logbook-entry.in::before { transform: scale(1); }
.logbook-entry:last-child { margin-bottom: 0; }

/* ---- Google reviews carousel (live, see assets/js/google-reviews.js) ---- */
/* JS-driven (translateX on .review-track), not native overflow-scroll — needed so the
   same track can both auto-advance and be nudged by the prev/next buttons, and so the
   index can wrap seamlessly for the infinite loop (see buildCarousel in the JS file). */
.reviews-carousel { position: relative; padding: 0 8px; }
.reviews-viewport { overflow: hidden; }
.review-track {
  display: flex; gap: 20px;
  transition: transform 550ms var(--ease-out);
}
.review-track.no-transition { transition: none; }
.review-card {
  flex: 0 0 320px; text-align: left; position: relative;
  background: var(--surface); border: 1px solid var(--line); border-radius: 16px;
  box-shadow: var(--shadow-card); padding: 28px 24px 24px;
  transition: box-shadow 200ms, transform 200ms;
}
.review-card:hover { box-shadow: 0 10px 30px rgba(14,30,43,0.12); transform: translateY(-3px); }
.review-card::before {
  content: '\201C'; position: absolute; top: 10px; right: 20px;
  font: 800 3.5rem/1 Georgia, serif; color: var(--line);
}
.review-stars { color: var(--brass-deep); font-size: 1.0625rem; letter-spacing: 2px; position: relative; z-index: 1; }
.review-text {
  margin-top: 12px; font-size: 0.9375rem; color: var(--ink-2); line-height: 1.6;
  position: relative; z-index: 1;
  /* Cards vary a lot in review length; clamping keeps every card the same height so the
     row reads as one aligned set rather than a ragged, mismatched grid. */
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden;
}
.review-author { display: flex; align-items: center; gap: 10px; margin-top: 18px; font-size: 0.875rem; }
.review-author img {
  border-radius: 50%; width: 38px; height: 38px; object-fit: cover;
  border: 2px solid var(--bg-2);
}
.review-author-fallback {
  width: 38px; height: 38px; border-radius: 50%; flex-shrink: 0;
  background: var(--navy-deep); color: #fff; font-weight: 800; font-size: 0.875rem;
  display: flex; align-items: center; justify-content: center;
}
.review-time { color: var(--ink-2); font-size: 0.8125rem; }
.review-footer {
  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px;
  margin-top: 24px; font-size: 0.875rem; color: var(--ink-2);
}
.review-footer a { color: var(--brass-deep); font-weight: 700; }

.reviews-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow-card);
  color: var(--navy-deep); cursor: pointer; transition: background 150ms, color 150ms, transform 150ms;
}
.reviews-nav:hover { background: var(--navy-deep); color: #fff; transform: translateY(-50%) scale(1.06); }
.reviews-nav:active { transform: translateY(-50%) scale(0.96); }
.reviews-nav--prev { left: -20px; }
.reviews-nav--next { right: -20px; }
@media (max-width: 720px) {
  .reviews-nav--prev { left: -6px; }
  .reviews-nav--next { right: -6px; }
  .reviews-nav { width: 36px; height: 36px; }
  .review-card { flex-basis: 260px; }
}

/* ---- Areas map (real Leaflet + OpenStreetMap tiles, see assets/js/areas-map.js) ---- */
#areas-map {
  width: 100%; height: 420px; border-radius: 20px; border: 1px solid var(--line);
  overflow: hidden; z-index: 0; /* keep Leaflet's internal z-indexing below the sticky header */
}
/* Leaflet's own tile container/panes are square by default; the radius above is clipped by
   overflow:hidden on #areas-map, but Leaflet re-applies its own layout on the inner .leaflet-
   container, so it needs the same radius or corner tiles show square behind the mask. */
.leaflet-container { border-radius: 20px; }
.area-pin span {
  display: block; width: 14px; height: 14px; border-radius: 50%;
  background: var(--navy-deep); border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0,0,0,0.4);
}
/* The one marker on the map that isn't a coverage-area dot — Seawolf's own registered
   office. Bigger, carries the favicon, and gets a soft beacon pulse (same nautical
   "lighthouse" motif as the voyage-timeline dots elsewhere on the homepage) so it reads
   as the map's home port rather than another area pin. */
.office-pin {
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%; position: relative;
  background: #fff; border: 3px solid var(--brass); box-shadow: 0 2px 8px rgba(0,0,0,0.35);
}
.office-pin img { width: 20px; height: 20px; border-radius: 50%; display: block; }
.office-pin::after {
  content: ''; position: absolute; inset: -6px; border-radius: 50%; border: 2px solid var(--brass);
  opacity: 0; pointer-events: none;
  animation: officePinPulse 2600ms var(--ease-out) infinite;
}
@keyframes officePinPulse {
  0%   { transform: scale(0.8); opacity: 0.6; }
  100% { transform: scale(1.7); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .office-pin::after { animation: none; } }
.leaflet-popup-content-wrapper { border-radius: 10px; font-family: var(--font-body); }
.leaflet-popup-content { margin: 12px 14px; font-size: 0.9375rem; }
.leaflet-popup-content a { color: var(--brass-deep); font-weight: 700; }
.leaflet-popup-content a:hover { text-decoration: underline; }

/* ---- Areas list (pill buttons beside the areas map, see index.html) ---- */
.areas-list { display: flex; flex-wrap: wrap; gap: 10px; }
.area-link {
  display: inline-flex; align-items: center; padding: 11px 20px;
  border-radius: 999px; background: var(--surface); border: 1.5px solid var(--line);
  color: var(--ink); font-weight: 700; font-size: 0.9375rem; text-decoration: none;
  transition: background 160ms var(--ease-out), border-color 160ms var(--ease-out), color 160ms var(--ease-out), transform 160ms var(--ease-out);
}
.area-link:hover { background: var(--navy); border-color: var(--navy); color: #fff; transform: translateY(-2px); }

/* ---- Areas CTA (below the pill list, see index.html) ---- */
.areas-cta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px;
  padding: 18px 22px; border-radius: var(--radius); background: var(--bg-2); border: 1px solid var(--line);
}
.areas-cta p { font-weight: 700; color: var(--ink); }

/* ---- FAQ accordion ---- */
.faq-item { border-bottom: 1px solid var(--line); }
.faq-q { width: 100%; display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 20px 0; font: 700 1.0625rem var(--font-body); text-align: left; color: var(--ink); }
.faq-q .plus { flex-shrink: 0; width: 22px; height: 22px; border: 1.5px solid var(--brass); border-radius: 50%; position: relative; }
.faq-q .plus::before, .faq-q .plus::after { content: ''; position: absolute; background: var(--brass); }
.faq-q .plus::before { top: 50%; left: 5px; right: 5px; height: 1.5px; transform: translateY(-50%); }
.faq-q .plus::after { left: 50%; top: 5px; bottom: 5px; width: 1.5px; transform: translateX(-50%); transition: opacity 150ms; }
.faq-item.open .plus::after { opacity: 0; }
.faq-a { max-height: 0; overflow: hidden; transition: max-height 300ms var(--ease-out); }
.faq-item.open .faq-a { max-height: 400px; }
.faq-a p { padding-bottom: 20px; }

/* ---- Breadcrumb ---- */
.breadcrumb { display: flex; align-items: center; gap: 8px; font: 600 0.8125rem var(--font-body); color: var(--ink-2); flex-wrap: wrap; }
.breadcrumb a:hover { color: var(--brass); }
.breadcrumb .sep { opacity: 0.5; }

/* ---- Form ---- */
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font: 600 0.8125rem var(--font-body); color: var(--ink-2); }
.field input, .field select, .field textarea {
  border: 1.5px solid var(--line); border-radius: 4px; padding: 12px 14px; background: var(--surface); color: var(--ink);
}
.field input:focus, .field select:focus, .field textarea:focus { border-color: var(--brass); outline: none; }
/* Honeypot: kept in the DOM (not display:none, some bots skip those) but off-screen and
   out of the tab order / AT tree, so it never reaches a real visitor. */
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form-notice {
  border: 1.5px solid var(--line); border-radius: var(--radius); padding: 14px 18px;
  font: 600 0.9375rem var(--font-body); margin-top: 24px;
}
.form-notice--success { border-color: var(--sea-glass); background: color-mix(in srgb, var(--sea-glass) 10%, var(--surface)); color: var(--ink); }
.form-notice--error { border-color: #C0392B; background: color-mix(in srgb, #C0392B 8%, var(--surface)); color: var(--ink); }

/* ---- Utility ---- */
.stack { display: flex; flex-direction: column; }
.row { display: flex; align-items: center; }
.gap-8 { gap: 8px; } .gap-12 { gap: 12px; } .gap-16 { gap: 16px; } .gap-24 { gap: 24px; }
.mt-8{margin-top:8px;} .mt-16{margin-top:16px;} .mt-24{margin-top:24px;} .mt-32{margin-top:32px;} .mt-48{margin-top:48px;}
.text-center { text-align: center; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; }

/* ---- Cookie consent ---- */
/* Built and injected by /assets/js/cookie-consent.js — the markup lives in JS,
   not here, so every page (generated or hand-written) gets it for free from
   the single <div id="cookie-consent-root"> in the shared footer partial. */
.cc-banner {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 4000;
  background: var(--navy-deep); border-top: 1px solid rgba(255,255,255,0.12);
  box-shadow: 0 -8px 24px rgba(11,31,46,0.22);
  transform: translateY(100%); transition: transform 320ms var(--ease-out);
}
.cc-banner.cc-visible { transform: translateY(0); }
.cc-banner-inner {
  max-width: var(--container); margin-inline: auto; padding: 20px var(--gutter);
  display: flex; flex-wrap: wrap; align-items: center; gap: 16px 24px;
}
.cc-banner-text { margin: 0; flex: 1 1 360px; font-size: 0.875rem; line-height: 1.6; color: rgba(255,255,255,0.85); }
.cc-banner-text a { color: var(--brass); font-weight: 700; border-bottom: 1.5px solid transparent; }
.cc-banner-text a:hover { border-bottom-color: var(--brass); }
.cc-banner-actions { display: flex; flex-wrap: wrap; gap: 10px; flex: 0 0 auto; }
.cc-banner .btn-secondary { color: rgba(255,255,255,0.92); border-color: rgba(255,255,255,0.55); }
.cc-banner .btn-secondary:hover { border-color: var(--brass); color: var(--brass); }

.cc-modal-overlay {
  position: fixed; inset: 0; z-index: 4100; background: rgba(11,31,46,0.55);
  display: flex; align-items: center; justify-content: center; padding: 20px;
  opacity: 0; pointer-events: none; transition: opacity 220ms var(--ease-out);
}
.cc-modal-overlay.cc-visible { opacity: 1; pointer-events: auto; }
.cc-modal-dialog {
  position: relative; width: 100%; max-width: 520px; max-height: calc(100vh - 40px); overflow-y: auto;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 24px 60px rgba(11,31,46,0.35); padding: 32px;
  transform: translateY(12px); transition: transform 220ms var(--ease-out);
}
.cc-modal-overlay.cc-visible .cc-modal-dialog { transform: translateY(0); }
.cc-modal-dialog h2 { font-size: 1.375rem; margin: 0 0 8px; }
.cc-modal-intro { font-size: 0.9375rem; margin: 0 0 24px; }
.cc-modal-close {
  position: absolute; top: 16px; right: 16px; width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; color: var(--ink-2); font-size: 0.9375rem;
  border: 1px solid var(--line); background: var(--surface);
}
.cc-modal-close:hover { border-color: var(--brass); color: var(--brass); }
.cc-modal-list { display: flex; flex-direction: column; gap: 18px; margin-bottom: 28px; }
.cc-modal-row {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding-bottom: 18px; border-bottom: 1px solid var(--line);
}
.cc-modal-row:last-child { border-bottom: none; padding-bottom: 0; }
.cc-modal-row-label { display: block; font-weight: 700; font-size: 0.9375rem; margin-bottom: 4px; cursor: pointer; }
.cc-modal-row-desc { margin: 0; font-size: 0.8125rem; color: var(--ink-2); max-width: 34ch; }
.cc-modal-actions { display: flex; flex-wrap: wrap; gap: 12px; }
.cc-modal-actions .btn { flex: 1 1 160px; justify-content: center; }

.cc-toggle { position: relative; flex-shrink: 0; display: inline-block; width: 44px; height: 26px; cursor: pointer; margin-top: 2px; }
.cc-toggle-input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.cc-toggle-track {
  position: absolute; inset: 0; background: var(--line); border-radius: 999px; transition: background 180ms var(--ease-out);
}
.cc-toggle-track::after {
  content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(11,31,46,0.3); transition: transform 180ms var(--ease-out);
}
.cc-toggle-input:checked + .cc-toggle-track { background: var(--brass); }
.cc-toggle-input:checked + .cc-toggle-track::after { transform: translateX(18px); }
.cc-toggle-input:disabled + .cc-toggle-track { opacity: 0.6; cursor: not-allowed; }
.cc-toggle-input:focus-visible + .cc-toggle-track { outline: 2px solid var(--brass); outline-offset: 2px; }

@media (max-width: 640px) {
  /* Full-height-stacked (Manage / Reject / Accept, each its own full-width row) ate
     roughly half of a typical phone viewport. Manage + Reject share a row here, Accept
     all keeps its own row below — same DOM/tab order, two rows shorter overall. */
  .cc-banner-inner { padding: 14px 20px; gap: 12px 16px; }
  .cc-banner-text { font-size: 0.8125rem; }
  .cc-banner-actions { width: 100%; }
  .cc-banner-actions .btn { flex: 1 1 calc(50% - 5px); padding-block: 11px; }
  .cc-banner-actions .btn:last-child { flex: 1 1 100%; }
  .cc-modal-dialog { padding: 24px 20px; }
}
