/* ============================================================
   Ripple FZCO — marketing site · shared styles (Phase 1 migration)
   Changes vs. the prototype stylesheet:
   · Google Fonts @import replaced by self-hosted variable fonts
   · dark-section literal hexes promoted to named tokens (see :root)
   · <image-slot> rules replaced by .media / .ph (real <img> rendering)
   · .reveal initial-hidden state gated on .js (no-JS fallback)
   · slider timing driven by --slide-dur (JS reads the same value)
   · dead prototype blocks removed (sonar hero, .pagehead, .pf-grid)
   · B2B page styles folded in from b2b.html (bottom of file)
   ============================================================ */

@font-face{
  font-family:"Sora";
  src:url("../fonts/sora.woff2") format("woff2");
  font-weight:600 700; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"IBM Plex Sans";
  src:url("../fonts/plex.woff2") format("woff2");
  font-weight:400 600; font-style:normal; font-display:swap;
}

:root{
  --red:#C60E13;
  --red-deep:#9E0B0F;
  --accent-bright:#FF5A4D;
  --ink:#14110F;
  --muted:#6E6A66;
  --faint:#9A958F;
  --line:#E7E4DF;
  --bg:#FBFAF8;
  --bg-alt:#F4F1EC;
  --panel:#14110F;
  --panel-2:#1E1A17;

  /* rgb triplets for rgba() composition */
  --red-rgb:198,14,19;
  --accent-bright-rgb:255,90,77;
  --deep-rgb:10,8,7;

  /* dark-section palette (Phase 1 token promotion — was scattered literals) */
  --bg-deepest:#0A0807;
  --text-on-dark:#F4F1EC;
  --muted-on-dark:#B7B0A7;
  --soft-on-dark:#C9C3BA;
  --lead-on-dark:#D8D2C9;
  --faint-on-dark:#8A847B;
  --line-on-dark:#332D27;
  --line-on-dark-2:#3A342E;
  --on-dark-soft:#E8E3DC;
  --warm-white:#FFF7F2;

  /* themeable (Phase 4 admin theming target) */
  --nav-bg:17,14,12;
  --nav-bg-scrolled:15,12,10;
  --nav-link:#E8E3DC;
  --nav-active:198,14,19;
  --scrim-op:1;
  --scrim-tint:198,14,19;

  --display:"Sora",sans-serif;
  --body:"IBM Plex Sans",sans-serif;

  --nav-h:76px;
  --maxw:1240px;
  --pad-x:clamp(22px, 5vw, 64px);
  --radius:18px;

  /* single source of slider timing — site.js derives its autoplay from this */
  --slide-dur:6s;

  --shadow-sm:0 1px 2px rgba(20,17,15,.05), 0 4px 16px rgba(20,17,15,.04);
  --shadow-md:0 10px 40px rgba(20,17,15,.10);
}

*{ box-sizing:border-box; }
html{ scroll-behavior:smooth; scroll-padding-top:calc(var(--nav-h) + 12px); }
body{
  margin:0; background:var(--bg); color:var(--ink);
  font-family:var(--body); font-size:18px; line-height:1.6;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
img{ max-width:100%; display:block; }
a{ color:inherit; text-decoration:none; }

/* ---------- type ---------- */
h1,h2,h3,h4{ font-family:var(--display); font-weight:700; margin:0; line-height:1.05; letter-spacing:-.02em; }
h1{ font-size:clamp(36px, 5.2vw, 66px); line-height:1.04; letter-spacing:-.03em; }
h2{ font-size:clamp(29px, 3.8vw, 48px); line-height:1.08; }
h3{ font-size:clamp(21px, 2.2vw, 28px); font-weight:600; letter-spacing:-.01em; }
p{ margin:0; }
.lead{ font-size:clamp(19px, 1.6vw, 23px); line-height:1.55; color:var(--muted); }
.muted{ color:var(--muted); }
.accent{ color:var(--red); }
.balance{ text-wrap:balance; }
.pretty{ text-wrap:pretty; }

.eyebrow{
  font-family:var(--body); font-size:14px; font-weight:600; line-height:1.6;
  letter-spacing:.2em; text-transform:uppercase; color:var(--red);
  /* flex-start, not center: the rule and the text are ONE flex line (nowrap),
     so centring aligns the rule against the whole multi-line text block and it
     lands mid-phrase on line two. Anchoring to the top and offsetting by half a
     line puts it on the first line — visually identical when it does not wrap. */
  margin:0; display:inline-flex; align-items:flex-start; gap:14px;
}
.eyebrow::before{ content:""; width:34px; height:2px; background:currentColor;
  flex:0 0 auto; margin-top:calc(.8em - 1px); }
.eyebrow.nobar::before{ display:none; }
/* Mobile: .2em tracking at 14px is a desktop-scale choice that was never given
   a mobile value — it made the 52-character homepage eyebrow ~597px wide, so it
   wrapped to three lines at 390px. 13px/.12em brings that to ~430px: two lines
   at 390px, one at 430px. */
@media (max-width:720px){
  .eyebrow{ font-size:13px; letter-spacing:.12em; gap:10px; }
  .eyebrow::before{ width:26px; }
}
.eyebrow.muted{ color:var(--faint); }
.eyebrow.light{ color:var(--accent-bright); }

/* ---------- layout ---------- */
.wrap{ max-width:var(--maxw); margin:0 auto; padding-inline:var(--pad-x); }
section{ position:relative; }
/* The 9vw term only overtakes the minimum above ~800px, so every phone got the
   full 72px top AND bottom — 144px between sections, ~17% of a 844px viewport
   and ~1,440px of pure padding on /products. Lowering the MINIMUM only affects
   viewports under ~622px; desktop is untouched. */
.section{ padding-block:clamp(56px, 9vw, 128px); }
.section.tight{ padding-block:clamp(40px, 6vw, 88px); }
.section.alt{ background:var(--bg-alt); }
.dark{ background:var(--panel); color:var(--text-on-dark); }
.dark .lead, .dark .muted{ color:var(--muted-on-dark); }
.dark h1,.dark h2,.dark h3{ color:var(--text-on-dark); }
.grid{ display:grid; gap:clamp(28px, 4vw, 72px); }
.split{ grid-template-columns:1fr 1fr; align-items:center; }
@media (max-width:880px){ .split{ grid-template-columns:1fr; } }

.stack-sm > * + *{ margin-top:14px; }
.stack   > * + *{ margin-top:22px; }
.stack-lg> * + *{ margin-top:34px; }

/* ---------- buttons ---------- */
.btn{
  display:inline-flex; align-items:center; gap:10px; cursor:pointer;
  font-family:var(--body); font-weight:600; font-size:16px; letter-spacing:.01em;
  padding:15px 28px; border-radius:999px; border:1.6px solid transparent;
  transition:transform .2s ease, background .2s ease, border-color .2s ease, color .2s ease;
  white-space:nowrap;
}
.btn .arw{ transition:transform .2s ease; }
.btn:hover .arw{ transform:translateX(4px); }
.btn-primary{ background:var(--red); color:#fff; border-color:var(--red); }
.btn-primary:hover{ background:var(--red-deep); border-color:var(--red-deep); transform:translateY(-2px); }
.btn-ghost{ background:transparent; color:var(--ink); border-color:var(--line); }
.btn-ghost:hover{ border-color:var(--ink); transform:translateY(-2px); }
.dark .btn-ghost{ color:var(--text-on-dark); border-color:var(--line-on-dark-2); }
.dark .btn-ghost:hover{ border-color:var(--text-on-dark); }
.btn-light{ background:#fff; color:var(--ink); border-color:#fff; }
.btn-light:hover{ transform:translateY(-2px); background:var(--bg-alt); border-color:var(--bg-alt); }

/* ---------- nav (dark header) ---------- */
.nav{
  position:fixed; top:0; left:0; right:0; height:var(--nav-h); z-index:100;
  display:flex; align-items:center;
  background:rgba(var(--nav-bg),.72); backdrop-filter:blur(16px);
  border-bottom:1px solid rgba(255,255,255,.08);
  box-shadow:inset 0 2px 0 var(--red);
  transition:background .3s ease, border-color .3s ease;
}
.nav.scrolled{ background:rgba(var(--nav-bg-scrolled),.94); border-bottom-color:rgba(255,255,255,.12); }
.nav-inner{ max-width:var(--maxw); width:100%; margin:0 auto; padding-inline:var(--pad-x);
  display:flex; align-items:center; justify-content:space-between; gap:24px; }
/* The link was only as tall as the 30px mark inside it. Padding lifts the
   target past 44px; the header is 76px tall, so nothing moves. */
.nav-brand{ display:flex; align-items:center; min-height:44px; }
.nav-logo{ height:30px; width:auto; }
.nav-links{ display:flex; align-items:center; gap:4px; list-style:none; margin:0; padding:0; }
.nav-links a{
  font-size:15px; font-weight:500; color:var(--nav-link); padding:9px 16px; border-radius:999px;
  transition:background .18s ease, color .18s ease; position:relative;
}
.nav-links a:hover{ background:rgba(255,255,255,.08); color:#fff; }
.nav-links a.active{ color:#fff; background:rgba(var(--nav-active),.9); }
.nav-cta{ display:flex; align-items:center; gap:12px; }
.nav-cta .btn{ padding:11px 22px; font-size:15px; }
.nav-cta .btn-ghost{ color:var(--on-dark-soft); border-color:rgba(255,255,255,.24); }
.nav-cta .btn-ghost:hover{ color:#fff; border-color:#fff; background:transparent; }
/* 24px bars + 8px padding gave a 40x42 target. min-* lifts it past 44 on both
   axes without moving the bars. */
.nav-toggle{ display:none; background:none; border:0; cursor:pointer; padding:10px;
  min-width:44px; min-height:44px; }
.nav-toggle span{ display:block; width:24px; height:2px; background:var(--text-on-dark); margin:5px 0; transition:.25s; }
@media (max-width:940px){
  .nav-links, .nav-cta .btn-ghost{ display:none; }
  .nav-toggle{ display:block; }
  .nav.open .nav-links{
    display:flex; flex-direction:column; align-items:stretch; gap:2px;
    position:absolute; top:var(--nav-h); left:0; right:0;
    /* Fully opaque, not .98: the hero showed through behind the menu items and
       made them hard to read. A navigation overlay has no reason to be
       translucent. */
    background:#0F0C0A; border-bottom:1px solid rgba(255,255,255,.12); padding:14px var(--pad-x) 22px;
    box-shadow:0 18px 40px rgba(0,0,0,.45);
  }
  /* The flex item is the <li>; the <a> inside is inline, so the active pill and
     the tap target only covered the TEXT width. display:block makes the anchor
     fill its row, giving a full-width pill and a full-row ~50px target. */
  .nav.open .nav-links a{ display:block; padding:14px 16px; font-size:18px; }
}
/* At 360px the header measured ~388px of content against 316px available:
   logo ~110 + CTA ~190 (desktop padding/size) + toggle 44 + two 24px gaps.
   .nav-inner does not wrap, so it overflowed. Shrinking the CTA and halving the
   gap brings it to ~290px. The arrow is dropped as decoration that costs width. */
@media (max-width:720px){
  .nav-inner{ gap:12px; }
  /* 12px block padding, not 9px: 9px gave a 41px-tall button. The inline
     padding is what the width fix depended on and is unchanged. */
  .nav-cta .btn{ padding:12px 14px; font-size:13px; }
  .nav-cta .btn .arw{ display:none; }
}

/* ---------- hero CTA row (shared by sliders) ---------- */
.hero-cta{ display:flex; flex-wrap:wrap; gap:14px; margin-top:40px; }

/* ---------- hero slider ---------- */
/* svh, not vh: on mobile `vh` resolves against the TOOLBAR-LESS viewport, so
   with the toolbar showing the box is taller than the visible area and the
   fixed-height slide overflows sooner. svh tracks what is actually visible.
   The vh line stays first as the fallback. */
.hero-slider{ position:relative; min-height:clamp(600px, 94vh, 940px); overflow:hidden; background:var(--bg-deepest); }
.hero-slider{ min-height:clamp(600px, 94svh, 940px); }
.hero-slides{ position:absolute; inset:0; }
.hero-slide{ position:absolute; inset:0; opacity:0; visibility:hidden;
  transition:opacity 1s ease, visibility 1s ease; }
.hero-slide.active{ opacity:1; visibility:visible; z-index:1; }
.hero-slide .slide-bg{ position:absolute; inset:0; width:100%; height:100%;
  border-radius:0; background:var(--panel); z-index:0; }
/* subtle ken-burns on the active slide's image */
@media (prefers-reduced-motion:no-preference){
  .hero-slide.active .slide-bg{ animation:kenburns 9s ease-out both; }
}
@keyframes kenburns{ from{ transform:scale(1.08); } to{ transform:scale(1); } }
.slide-scrim{ position:absolute; inset:0; z-index:2; pointer-events:none;
  background:linear-gradient(180deg, rgba(var(--deep-rgb),calc(.62*var(--scrim-op))) 0%, rgba(var(--deep-rgb),calc(.34*var(--scrim-op))) 34%, rgba(var(--deep-rgb),calc(.52*var(--scrim-op))) 70%, rgba(var(--deep-rgb),calc(.9*var(--scrim-op))) 100%),
             radial-gradient(120% 90% at 18% 82%, rgba(var(--scrim-tint),calc(.30*var(--scrim-op))) 0%, rgba(var(--scrim-tint),0) 46%); }
.hero-slide .wrap{ position:relative; z-index:3; height:100%; display:flex; flex-direction:column;
  justify-content:flex-end; padding-bottom:clamp(96px, 12vh, 168px); padding-top:calc(var(--nav-h) + 40px);
  pointer-events:none; }
/* .hero-slide is position:absolute so this box has a FIXED height. With plain
   flex-end, content taller than the box overflows past the TOP edge — through
   the padding-top and under the fixed header. `safe` falls back to start
   alignment exactly in that case, which is the bug's definition. Declared after
   the plain value so browsers without `safe` keep today's behaviour. */
.hero-slide .wrap{ justify-content:safe flex-end; }
.hero-slide .wrap a, .hero-slide .wrap button{ pointer-events:auto; }
.hero-slide .slide-inner{ max-width:900px; }
.hero-slide .eyebrow{ opacity:0; transform:translateY(20px); }
.hero-slide h1{ color:#fff; max-width:16ch; margin-top:20px; opacity:0; transform:translateY(24px); }
.hero-slide h1 .accent, .hero-slide .accent{ color:var(--accent-bright); }
.hero-slide .lead{ color:var(--lead-on-dark); max-width:56ch; margin-top:22px; font-size:clamp(18px,1.5vw,22px);
  opacity:0; transform:translateY(24px); }
.hero-slide .hero-cta{ display:flex; flex-wrap:wrap; gap:14px; margin-top:38px; opacity:0; transform:translateY(24px); }
.hero-slide.active .eyebrow{ animation:slideUp .7s cubic-bezier(.16,.84,.34,1) .25s forwards; }
.hero-slide.active h1{ animation:slideUp .8s cubic-bezier(.16,.84,.34,1) .36s forwards; }
.hero-slide.active .lead{ animation:slideUp .8s cubic-bezier(.16,.84,.34,1) .5s forwards; }
.hero-slide.active .hero-cta{ animation:slideUp .8s cubic-bezier(.16,.84,.34,1) .64s forwards; }
@keyframes slideUp{ to{ opacity:1; transform:none; } }
.hero-slide .btn-ghost{ color:#fff; border-color:rgba(255,255,255,.32); }
.hero-slide .btn-ghost:hover{ color:#fff; border-color:#fff; }
@media (prefers-reduced-motion:reduce){
  .hero-slide .eyebrow, .hero-slide h1, .hero-slide .lead, .hero-slide .hero-cta{ opacity:1; transform:none; animation:none; }
}
/* controls */
.slider-ui{ position:absolute; left:0; right:0; bottom:0; z-index:5; }
.slider-bar{ max-width:var(--maxw); margin:0 auto; padding:0 var(--pad-x) clamp(30px,5vh,48px);
  display:flex; align-items:center; justify-content:space-between; gap:24px; }
.slider-dots{ display:flex; gap:14px; align-items:center; }
.slider-dots button{ position:relative; height:3px; width:56px; border:0; padding:0; cursor:pointer;
  background:rgba(255,255,255,.24); border-radius:2px; transition:background .25s ease; }
/* The visual is a 3px bar, which is a 3px tap target. ::after adds an invisible
   44px-tall hit area without changing the appearance. Vertical only, so
   neighbouring dots (14px apart) cannot overlap each other. */
.slider-dots button::after{ content:""; position:absolute; left:0; right:0; top:-21px; bottom:-21px; }
.slider-dots button:hover{ background:rgba(255,255,255,.4); }
.slider-dots button .fill{ position:absolute; inset:0; width:0; background:var(--red); border-radius:2px; }
.slider-dots button.active .fill{ background:var(--accent-bright); }
.slider-dots button.active.run .fill{ width:100%; transition:width var(--slide-dur) linear; }
.slider-arrows{ display:flex; gap:10px; }
.slider-arrows button{ width:52px; height:52px; border-radius:50%; cursor:pointer;
  background:rgba(255,255,255,.06); border:1.5px solid rgba(255,255,255,.28); color:#fff;
  font-size:19px; display:flex; align-items:center; justify-content:center;
  transition:background .2s ease, border-color .2s ease, transform .2s ease; }
.slider-arrows button:hover{ background:var(--red); border-color:var(--red); transform:translateY(-2px); }
.slide-count{ position:absolute; right:var(--pad-x); top:calc(var(--nav-h) + 44px); z-index:5;
  font-family:var(--display); font-weight:600; font-size:15px; letter-spacing:.14em; color:rgba(255,255,255,.7); }
.slide-count b{ color:#fff; font-size:20px; }
@media (max-width:620px){
  .slider-arrows{ display:none; }
  .slider-dots button{ width:38px; }
  .slide-count{ display:none; }
}

/* who-we-are — image-led layout */
.whoweare{ grid-template-columns:1.5fr 1fr; align-items:center;
  gap:clamp(32px,5vw,72px); }
.whoweare .wwa-media .media{ width:100%; }
.whoweare .wwa-title{ font-size:clamp(23px, 2.6vw, 34px); font-weight:600; letter-spacing:-.015em;
  line-height:1.14; }
@media (max-width:880px){
  .whoweare{ grid-template-columns:1fr; }
  .whoweare .wwa-media{ order:-1; }
}

/* breadcrumbs (page heads) */
.pagehead-inner{ position:relative; z-index:2; }
.crumbs{ font-size:13px; letter-spacing:.14em; text-transform:uppercase; color:var(--faint-on-dark); margin-bottom:22px; }
/* 13px text is a 17px-tall link. The negative margin cancels the padding, so
   the crumb line keeps its position and only the hit area grows. */
.crumbs a{ display:inline-block; padding-block:14px; margin-block:-14px; }
.crumbs a:hover{ color:var(--text-on-dark); }

/* page hero with background slider (interior pages) */
.pagehead-slider{ position:relative; overflow:hidden; background:var(--bg-deepest);
  min-height:clamp(440px, 62vh, 660px); display:flex; }
.pagehead-slider{ min-height:clamp(440px, 62svh, 660px); }
.pagehead-slider .hero-slides{ position:absolute; inset:0; }
.pagehead-slider .pagehead-inner{ position:relative; z-index:3; width:100%; align-self:flex-end;
  max-width:var(--maxw); margin:0 auto; padding-inline:var(--pad-x);
  padding-top:calc(var(--nav-h) + 40px); padding-bottom:clamp(56px, 8vh, 104px);
  pointer-events:none; }
.pagehead-slider .pagehead-inner a, .pagehead-slider .pagehead-inner button{ pointer-events:auto; }
.pagehead-slider h1{ color:#fff; max-width:18ch; }
.pagehead-slider .lead{ color:var(--lead-on-dark); max-width:64ch; margin-top:22px; }
.pagehead-slider .crumbs{ color:rgba(255,255,255,.6); }
.pagehead-slider .slider-ui{ position:absolute; }
.pagehead-slider .slider-bar{ padding-bottom:clamp(22px,4vh,34px); }
/* on-image chips + ghost buttons (was inline literal colors in the prototype) */
.pagehead-slider .chip{ background:transparent; border-color:var(--line-on-dark-2); color:var(--on-dark-soft); }
.pagehead-slider .btn-ghost{ color:var(--warm-white); }

/* ---------- media slots (replaces the design tool's <image-slot>) ---------- */
.media{ display:block; position:relative; overflow:hidden; background:#EEEAE4; border-radius:var(--radius); }
.media img, .media video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
.dark .media, .media.on-dark{ background:#221E1A; }
/* intentional placeholder for slots awaiting imagery — no editor UI, just a quiet gradient */
.media.ph{ background:linear-gradient(135deg,#EEEAE4 0%,#E4DED5 55%,#DBD3C7 100%); }
.dark .media.ph, .media.on-dark.ph{ background:linear-gradient(135deg,#221E1A 0%,#2A241E 60%,#332C24 100%); }
.slide-bg.media{ border-radius:0; }
/* page-head background placeholder: the brand's own dark red-glow gradient */
.slide-bg.ph{ background:
  radial-gradient(circle at 74% 82%, rgba(var(--red-rgb),.34) 0%, rgba(var(--red-rgb),0) 42%),
  radial-gradient(circle at 50% 34%, #1C1714 0%, var(--panel) 55%, var(--bg-deepest) 100%); }
.r-20{ border-radius:20px; }
/* let aspect-ratio drive height */
.ratio-4-3, .ratio-3-2, .ratio-1-1, .ratio-3-4, .ratio-16-9{ height:auto; width:100%; }
.ratio-4-3{ aspect-ratio:4/3; }
.ratio-3-2{ aspect-ratio:3/2; }
.ratio-1-1{ aspect-ratio:1/1; }
.ratio-3-4{ aspect-ratio:3/4; }
.ratio-16-9{ aspect-ratio:16/9; }

/* ---------- cards ---------- */
.card{ background:#fff; border:1.5px solid var(--line); border-radius:var(--radius);
  padding:clamp(26px,3vw,38px); box-shadow:var(--shadow-sm); }
.card-ink{ background:var(--ink); color:#fff; border:none; }
.card-ink h3{ color:#fff; }
.card-ink .muted{ color:var(--muted-on-dark); }
/* ink-card innards (was inline literal colors in the prototype) */
.card-ink .ink-kicker{ color:var(--muted-on-dark); }
.card-ink .ink-row{ color:var(--text-on-dark); }
.card-ink .ink-div{ height:1px; background:var(--line-on-dark); }

.chips{ display:flex; flex-wrap:wrap; gap:12px; }
.chip{ font-size:15px; font-weight:500; color:var(--ink); background:#fff;
  border:1.5px solid var(--line); border-radius:999px; padding:9px 20px; }
.dark .chip{ background:transparent; border-color:var(--line-on-dark-2); color:var(--on-dark-soft); }
.chip.on{ border-color:var(--red); color:var(--red); }

/* stat row */
.stats{ display:grid; grid-template-columns:repeat(4,1fr); border-top:2px solid var(--line); }
@media (max-width:820px){ .stats{ grid-template-columns:repeat(2,1fr); } }
.stat{ padding:36px 32px 6px 0; border-right:1px solid var(--line); }
.stat:last-child{ border-right:none; }
@media (max-width:820px){ .stat{ padding:30px 24px 6px; border-right:1px solid var(--line);
  border-bottom:1px solid var(--line); } .stat:nth-child(2n){ border-right:none; } }
/* Two columns never got a third step down. At 390px that left a 124px label
   column — "Established in Dubai's Airport Freezone (DAFZA)" over six lines —
   and at 320px the 46px numeral plus 48px of padding no longer fit the 138px
   track at all, so the row set the document 14px wider than the viewport and
   every page scrolled sideways. One column below 520px. */
@media (max-width:520px){
  .stats{ grid-template-columns:1fr; }
  .stat{ padding:26px 0 10px; border-right:none; }
  .stat:last-child{ border-bottom:none; }
  .stat .lbl{ max-width:none; }
}
.stat .num{ font-family:var(--display); font-weight:700; font-size:clamp(46px,5.4vw,80px);
  color:var(--red); line-height:1; letter-spacing:-.03em; }
.stat .num small{ font-size:.5em; font-weight:600; }
.stat .lbl{ margin-top:14px; color:var(--muted); font-size:16px; line-height:1.45; max-width:24ch; }
.dark .stats{ border-top-color:var(--line-on-dark); }
.dark .stat{ border-right-color:var(--line-on-dark); }
.dark .stat .lbl{ color:var(--muted-on-dark); }

/* portfolio — editorial hairline grid */
.pf2{ display:grid; grid-template-columns:repeat(4,1fr); gap:1px;
  background:var(--line); border:1px solid var(--line); border-radius:20px; overflow:hidden; }
@media (max-width:900px){ .pf2{ grid-template-columns:repeat(2,1fr); } }
/* 640, not 520: each cell spends 60px on padding and another 52px on the
   number and its gap, so a 520-620px viewport left roughly 142px for the copy
   — "Mobile & Computing" broke across three lines. Eight cells divide evenly
   by one, two and four, so no column count here leaves a hole in the grid. */
@media (max-width:640px){ .pf2{ grid-template-columns:1fr; } }
.pf2-cell{ position:relative; background:var(--bg); padding:32px 30px 34px; min-height:210px;
  display:flex; align-items:flex-start; gap:20px; overflow:hidden;
  transition:background .3s ease, color .25s ease; text-decoration:none; color:inherit; }
.pf2-cell::after{ content:""; position:absolute; left:0; right:0; bottom:0; height:3px;
  background:var(--red); transform:scaleX(0); transform-origin:left; transition:transform .35s cubic-bezier(.16,.84,.34,1); }
.pf2-cell:hover{ background:#fff; }
.pf2-cell:hover::after{ transform:scaleX(1); }
.pf2-num{ font-family:var(--display); font-weight:700; font-size:15px; letter-spacing:.06em;
  color:var(--red); line-height:1; padding-top:5px; flex-shrink:0; transition:color .25s ease; }
.pf2-body h3{ font-size:23px; line-height:1.12; letter-spacing:-.01em; }
.pf2-body p{ margin-top:12px; font-size:15px; color:var(--muted); line-height:1.5; }
.pf2-cell:hover .pf2-body h3{ color:var(--red); }
/* Tablet landscape keeps the designed four-across rhythm, but a 230px cell
   cannot also spend 52px placing the number beside the text. Stacking the
   number above the heading returns that width to the copy — 126px becomes
   ~186px — without changing the column count, so the grid stays whole. */
@media (min-width:901px) and (max-width:1140px){
  .pf2-cell{ flex-direction:column; gap:14px; padding:28px 24px 30px; }
  .pf2-num{ padding-top:0; }
}

/* CTA cell */
.pf2-cta{ background:var(--ink); }
.pf2-cta:hover{ background:#000; }
.pf2-cta::after{ display:none; }
.pf2-cta .pf2-num{ color:var(--accent-bright); }
.pf2-cta .pf2-body h3{ color:#fff; }
.pf2-cta:hover .pf2-body h3{ color:#fff; }
.pf2-cta .pf2-body p{ color:var(--muted-on-dark); }
.pf2-cta .pf2-go{ display:inline-flex; align-items:center; gap:9px; margin-top:20px;
  font-size:14px; font-weight:600; letter-spacing:.02em; color:#fff; }
.pf2-cta .arw{ transition:transform .25s ease; }
.pf2-cta:hover .arw{ transform:translateX(5px); }

/* category detail cards (product shots) */
.cat-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:24px; }
.cat-grid.four{ grid-template-columns:repeat(4,1fr); }
@media (max-width:900px){ .cat-grid, .cat-grid.four{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:520px){ .cat-grid, .cat-grid.four{ grid-template-columns:1fr; } }
.cat-card figcaption{ display:flex; align-items:baseline; justify-content:space-between; gap:14px;
  margin-top:18px; padding-top:16px; border-top:2px solid var(--line); }
.cat-card h3{ font-size:22px; }
.cat-tag{ font-size:13px; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--red); white-space:nowrap; }

/* numbered feature list */
/* The intro column stays beside the scrolling feature list on desktop. Below
   880px the .split grid collapses to ONE column, where a sticky intro has no
   neighbouring column to stay beside and instead scrolls over the features —
   which is what overlapped the heading with "01 Strategic location". Sticky is
   applied only where there are two columns. */
.feat-intro{ position:sticky; top:calc(var(--nav-h) + 32px); }
@media (max-width:880px){ .feat-intro{ position:static; top:auto; } }
/* 150px is a lot of vertical space in a single-column layout. */
@media (max-width:720px){ .motif{ width:96px; height:96px; } }
.feat{ display:flex; gap:32px; align-items:baseline; padding:26px 0; border-top:1px solid var(--line); }
.feat:last-child{ border-bottom:1px solid var(--line); }
.feat .n{ font-family:var(--display); font-weight:700; font-size:36px; color:var(--faint);
  min-width:70px; letter-spacing:-.02em; }
/* 70px + a 32px gap eats 102px of a 346px line on a phone, squeezing the
   heading into two lines. Reclaim it for the text. */
@media (max-width:720px){
  .feat{ gap:16px; padding:20px 0; }
  .feat .n{ min-width:44px; font-size:28px; }
}
.feat.on .n{ color:var(--red); }
.feat.on{ border-bottom:2px solid var(--red); }
.feat h3{ font-size:24px; }
.feat p{ margin-top:8px; color:var(--muted); }

/* value grid (sourcing 2x2) */
.val-grid{ display:grid; grid-template-columns:1fr 1fr; gap:18px; }
@media (max-width:560px){ .val-grid{ grid-template-columns:1fr; } }
/* Between 880 and 1140 the .split parent is still two columns, so a 2×2 value
   grid reads as four columns across the page — 159px of copy per card on an
   iPad in landscape. A single column here; the pair returns above 1140, where
   the parent column is wide enough to carry it. Declared after the 560px rule
   so the two never fight over the same viewport. */
@media (min-width:881px) and (max-width:1140px){ .val-grid{ grid-template-columns:1fr; } }
.val{ border-top:3px solid var(--red); padding:26px 26px; background:#fff; border-radius:0 0 14px 14px;
  box-shadow:var(--shadow-sm); }
.val h3{ font-size:22px; } .val p{ margin-top:10px; font-size:15px; color:var(--muted); }

/* mission cards */
.mission{ display:grid; grid-template-columns:repeat(3,1fr); gap:24px; }
@media (max-width:820px){ .mission{ grid-template-columns:1fr; } }
.mcard{ display:flex; flex-direction:column; border:1.5px solid var(--line); border-radius:20px;
  padding:40px 34px; background:#fff; min-height:340px; }
.mcard .mnum{ font-family:var(--display); font-weight:700; font-size:72px; color:var(--red); line-height:1; }
.mcard .bar{ width:52px; height:3px; background:var(--red); margin-top:22px; }
.mcard .mbody{ margin-top:auto; } .mcard .mbody p{ margin-top:14px; color:var(--muted); }
.mcard.ink{ background:var(--ink); border:none; }
.mcard.ink .mnum{ color:var(--accent-bright); } .mcard.ink .bar{ background:var(--accent-bright); }
.mcard.ink h3{ color:#fff; } .mcard.ink .mbody p{ color:var(--muted-on-dark); }

/* leadership */
.leaders{ display:grid; grid-template-columns:1fr 1fr; gap:clamp(28px,5vw,72px); }
@media (max-width:720px){ .leaders{ grid-template-columns:1fr; max-width:420px; margin-inline:auto; } }
.leader .media{ width:100%; height:auto; aspect-ratio:4/5; border-radius:20px; }
.leader .role{ font-size:13px; font-weight:600; letter-spacing:.16em; text-transform:uppercase; color:var(--red); margin-top:24px; }
.leader h3{ font-size:30px; margin-top:8px; }
.leader p{ margin-top:12px; color:var(--muted); }

/* callout / vision band */
.band{ background:var(--panel); color:var(--text-on-dark); }
.band .big{ font-family:var(--display); font-weight:600; font-size:clamp(30px,4.4vw,58px);
  line-height:1.15; letter-spacing:-.02em; max-width:20ch; }
.band .big .accent{ color:var(--accent-bright); }
.band .lead{ color:var(--muted-on-dark); }

/* CTA band */
.cta-band{ position:relative; overflow:hidden; }
.cta-cards{ display:grid; grid-template-columns:1fr 1fr; gap:24px; }
@media (max-width:720px){ .cta-cards{ grid-template-columns:1fr; } }
/* A lone card spans the row and centres at a readable width instead of sitting
   in the left half. :only-child matches nothing while two cards are present, so
   this is inert in the two-card case. */
.cta-cards > .cta-card:only-child{ grid-column:1 / -1; justify-self:center; width:100%; max-width:720px; }
.cta-card{ display:flex; flex-direction:column; background:#1C1813; border:1.5px solid #342E27;
  border-radius:20px; padding:40px; transition:transform .25s ease, border-color .25s ease, background .25s ease; }
.cta-card:hover{ transform:translateY(-4px); border-color:#5A4D42; }
.cta-card--accent{ background:var(--red); border-color:var(--red); }
.cta-card--accent:hover{ background:var(--red-deep); border-color:var(--red-deep); }
.cta-kicker{ font-size:13px; font-weight:600; letter-spacing:.16em; text-transform:uppercase; color:#9A8E80; }
.cta-card--accent .cta-kicker{ color:rgba(255,255,255,.72); }
.cta-url{ font-family:var(--display); font-weight:600; font-size:clamp(28px,3vw,40px); color:var(--text-on-dark);
  margin-top:16px; word-break:break-word; }
.cta-go{ margin-top:auto; padding-top:34px; font-size:15px; font-weight:600; color:var(--soft-on-dark);
  display:inline-flex; align-items:center; gap:10px; }
.cta-card--accent .cta-go{ color:#fff; }
.cta-card .arw{ transition:transform .25s ease; }
.cta-card:hover .arw{ transform:translateX(5px); }

/* ---------- partner marquee ----------
   CSS only: no JS, so no inline script and no extra CSP hash.
   The track holds the padded logo set exactly TWICE, so translateX(-50%) is
   precisely one set width whatever the logo count (no hardcoded distance).
   Edge fade uses mask-image, which works on alpha and therefore sits against
   whatever section background the block is placed on. */
/* container-type makes 100cqw resolve against .pm. A percentage flex-basis
   could NOT be used: .pm-track is width:max-content, so a percentage would
   resolve against an indefinite size and collapse. */
.pm{ position:relative; overflow:hidden; container-type:inline-size;
  -webkit-mask-image:linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image:linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
/* gap:0 — the gap lives INSIDE each slot as padding, so N slots sum to exactly
   100cqw. A flex gap would add N gutters on top of N slots and overflow. */
/* Rows stack vertically and reuse the horizontal gap at half value; 72px
   between rows reads as far too much. Each row scrolls independently, so
   direction and the small speed variance are per row. */
.pm{ display:flex; flex-direction:column; row-gap:var(--pm-rowgap,36px); }
.pm-row{ min-width:0; }
.pm-track{ display:flex; align-items:center; width:max-content; gap:0;
  animation:pm-scroll var(--pm-speed,40s) linear infinite; will-change:transform; }
.pm-row--rtl .pm-track{ animation-direction:reverse; }
.pm--hoverpause .pm-track:hover{ animation-play-state:paused; }
@keyframes pm-scroll{ from{ transform:translateX(0); } to{ transform:translateX(-50%); } }

/* Fixed-width slots: every logo occupies the same share of the viewport, so a
   square symbol and a wide wordmark read at comparable visual weight instead of
   the wordmark dominating at equal height. */
/* Two insets compose inside the slot: half the between-logo gap on each side,
   plus the per-logo padding. The slot width is unchanged, so slots still sum to
   exactly 100cqw — only the content box inside each one shrinks. */
.pm-item{ display:flex; align-items:center; justify-content:center;
  flex:0 0 calc(100cqw / var(--pm-per, 6));
  padding-inline:calc(var(--pm-gap,72px) / 2 + var(--pm-pad, 0px)); }
.pm-link{ display:flex; align-items:center; max-width:100%; }
/* Height, not transform: per-logo scale must not break vertical alignment or
   overflow the strip. The inline height carries the scale multiplier;
   max-width keeps a wide wordmark inside its slot, shrinking it if needed. */
.pm-logo{ display:block; width:auto; max-width:100%; height:var(--pm-h,40px);
  object-fit:contain; transition:filter .3s ease, opacity .3s ease; }
@media (max-width:720px){
  /* The slot narrows on a phone but the author's gap does not: at 320px with
     the defaults a 107px slot spent 72px on padding and left 35px for the
     logo, so every wordmark shrank to a smear. Cap the inset at a third of the
     slot, which keeps two thirds for the mark. A gap already smaller than that
     is the author's choice and passes through untouched. */
  .pm-item{ flex-basis:calc(100cqw / var(--pm-per-m, 3));
    padding-inline:min(
      calc(var(--pm-gap,72px) / 2 + var(--pm-pad, 0px)),
      calc(100cqw / var(--pm-per-m, 3) / 6)); }
}
/* Without container queries, fall back to the previous natural-width items. */
@supports not (container-type: inline-size){
  .pm-track{ gap:var(--pm-gap,72px); }
  /* No fixed slots here, so the gap comes from the flex gap above and only the
     per-logo padding stays as an inset. */
  .pm-item{ flex:0 0 auto; padding-inline:var(--pm-pad, 0px); }
  .pm-logo{ max-width:none; }
}
.pm--grey .pm-logo{ filter:grayscale(1); opacity:.72; }
/* invert: force any logo to a flat light silhouette for dark sections. */
.pm--invert .pm-logo{ filter:brightness(0) invert(1); opacity:.78; }
.pm--grey.pm--invert .pm-logo{ filter:grayscale(1) brightness(0) invert(1); opacity:.78; }
/* Colour on hover is PER LOGO; pause is per strip. Both may be active. */
.pm--hovercolor .pm-item:hover .pm-logo{ filter:none; opacity:1; }
.pm-link:focus-visible{ outline:2px solid var(--red); outline-offset:6px; border-radius:4px; }

/* Reduced motion: do not freeze the strip, that clips logos mid-view. Fall back
   to a static centred wrapped row showing each partner exactly once, so the
   padded repeats and the duplicate set are removed rather than merely stopped. */
@media (prefers-reduced-motion:reduce){
  .pm{ -webkit-mask-image:none; mask-image:none; }
  .pm-track{ animation:none; width:auto; flex-wrap:wrap; justify-content:center;
    row-gap:clamp(20px,3vw,32px); }
  /* Natural widths, not fixed slots: a static wrapped row should not be forced
     into a rigid N-across grid. Only the capped list survives, once. */
  .pm-item{ flex:0 0 auto; }
  .pm-item:not(.pm-item--primary){ display:none; }
  /* The capped list ONCE IN TOTAL, not once per row. Only row 1 carries
     primaries, so hiding the other rows also removes their empty gaps. */
  .pm-row:not(:first-child){ display:none; }
}

/* ---------- forms ---------- */
.form{ display:grid; grid-template-columns:1fr 1fr; gap:20px; }
.field{ display:flex; flex-direction:column; gap:8px; }
.field.full{ grid-column:1 / -1; }
@media (max-width:640px){ .form{ grid-template-columns:1fr; } .field{ grid-column:1 / -1 !important; } }
.field label{ font-size:14px; font-weight:600; letter-spacing:.02em; color:var(--ink); }
.field .req{ color:var(--red); }
.field input, .field select, .field textarea{
  font-family:var(--body); font-size:16px; color:var(--ink); background:#fff;
  border:1.5px solid var(--line); border-radius:12px; padding:14px 16px; width:100%;
  transition:border-color .18s ease, box-shadow .18s ease; }
.field textarea{ resize:vertical; min-height:140px; }
.field input:focus, .field select:focus, .field textarea:focus{
  outline:none; border-color:var(--red); box-shadow:0 0 0 3px rgba(var(--red-rgb),.12); }
.form-note{ font-size:14px; color:var(--faint); }
.form-success{ display:none; background:#fff; border:1.5px solid var(--line); border-left:4px solid var(--red);
  border-radius:14px; padding:26px 28px; }
.form-success.show{ display:block; }
.form-success h3{ font-size:22px; } .form-success p{ margin-top:8px; color:var(--muted); }

/* ---------- contact enquiry layout ----------
   Was an inline style on the block, which no breakpoint could reach. Declared
   after .grid so its own gap wins at equal specificity.
   min-width:0 matters as much as the breakpoint: a grid item's automatic
   minimum size is its min-content, and an <input> with no size attribute
   min-contents to about twenty characters — that is what widened the whole
   document rather than letting the 1.5fr track shrink. */
.enquiry-grid{ grid-template-columns:1.5fr 1fr; gap:clamp(40px,6vw,88px); align-items:start; }
.enquiry-grid > *{ min-width:0; }
/* 760px, not the 880px used by .split: at 768 (iPad mini portrait) the details
   card still holds a comfortable 206px of text, and stacking there would cost
   a whole screen of height for no gain. Below 760 the card drops under 200px
   and the two-column form beside it starts breaking words. */
@media (max-width:760px){ .enquiry-grid{ grid-template-columns:1fr; } }

/* contact detail list */
.cdetails{ display:flex; flex-direction:column; gap:28px; }
/* Stacked under the form, a single column of five short values leaves most of
   the card empty. Two columns use the width the stack just freed up. */
@media (min-width:520px) and (max-width:760px){
  .cdetails{ display:grid; grid-template-columns:1fr 1fr; gap:26px 32px; }
}
.cdetail .k{ font-size:13px; font-weight:600; letter-spacing:.14em; text-transform:uppercase; color:var(--faint); }
.cdetail .v{ font-size:18px; margin-top:8px; line-height:1.5; }
/* An inline <a> is only as tall as its text — 23px here. The padding lifts the
   target past 44px and the matching negative margin keeps the list rhythm. */
.cdetail a.v{ display:inline-block; padding-block:11px; margin-block:-11px; }
.cdetail a.v:hover{ color:var(--red); }

/* ---------- footer ---------- */
.footer{ background:var(--panel); color:var(--muted-on-dark); padding-block:clamp(56px,7vw,88px) 34px; }
.footer-top{ display:grid; grid-template-columns:1.6fr 1fr 1fr 1fr; gap:40px; }
@media (max-width:880px){ .footer-top{ grid-template-columns:1fr 1fr; gap:36px 24px; } }
@media (max-width:520px){ .footer-top{ grid-template-columns:1fr; } }
.footer-brand img{ height:30px; }
.footer-brand p{ margin-top:20px; max-width:34ch; font-size:15px; line-height:1.6; color:var(--faint-on-dark); }
.footer h4{ font-family:var(--body); font-size:13px; font-weight:600; letter-spacing:.14em;
  text-transform:uppercase; color:var(--text-on-dark); }
.footer .fcol ul{ list-style:none; margin:16px 0 0; padding:0; display:flex; flex-direction:column; gap:12px; }
.footer .fcol a{ font-size:15px; color:var(--muted-on-dark); transition:color .18s ease; }
.footer .fcol a:hover{ color:var(--text-on-dark); }
/* On a phone these were 19px-tall targets 12px apart — twelve links in one
   column, all under the minimum and none of them comfortably separated. Full
   -row blocks with block padding clear 44px; the gap shrinks to absorb most of
   the added height.
   pointer:coarse as well as a width, because target size follows the INPUT,
   not the layout: an iPad in landscape is 1024px wide and still a finger. A
   mouse-driven desktop keeps the tighter text-sized links. */
@media (max-width:880px), (pointer:coarse){
  .footer .fcol ul{ gap:2px; }
  .footer .fcol a{ display:block; padding-block:13px; }
}
.footer-bottom{ margin-top:clamp(44px,6vw,72px); padding-top:26px; border-top:1px solid #2A251F;
  display:flex; flex-wrap:wrap; gap:14px 28px; align-items:center; justify-content:space-between;
  font-size:13px; color:#7C766E; }
.footer-bottom .dot{ width:6px; height:6px; border-radius:50%; background:var(--red); display:inline-block; margin:0 10px; vertical-align:middle; }
.footer-credit a{ color:var(--soft-on-dark); border-bottom:1px solid rgba(255,255,255,.22); }
.footer-credit a:hover{ color:#fff; border-bottom-color:#fff; }
/* Three items on one flex line is cramped on a phone; the credit takes its own
   full-width line rather than being squeezed between the other two. */
@media (max-width:720px){ .footer-credit{ flex:0 0 100%; } }
/* The last link on every page was an 18px-tall target. Padding raises it;
   the underline still sits on the text because it is a border on the <a>. */
@media (max-width:880px), (pointer:coarse){ .footer-credit a{ display:inline-block; padding-block:13px; margin-block:-9px; } }

/* ---------- ripple motif ---------- */
.motif{ pointer-events:none; user-select:none; }
.motif path{ fill:none; stroke:var(--red); }
.motif circle{ fill:var(--red); }

/* ---------- scroll reveal ----------
   Initial hidden state is gated on .js (set by an inline <head> script) so
   content is fully visible when JavaScript is disabled or fails. */
.reveal{ transition:opacity .7s cubic-bezier(.16,.84,.34,1), transform .7s cubic-bezier(.16,.84,.34,1); }
.js .reveal{ opacity:0; transform:translateY(26px); }
.js .reveal.in{ opacity:1; transform:none; }
.reveal[data-d="1"]{ transition-delay:.08s; }
.reveal[data-d="2"]{ transition-delay:.16s; }
.reveal[data-d="3"]{ transition-delay:.24s; }
.reveal[data-d="4"]{ transition-delay:.32s; }
.reveal[data-d="5"]{ transition-delay:.40s; }
@media (prefers-reduced-motion:reduce){ .js .reveal{ opacity:1; transform:none; transition:none; } }
@media print{ .js .reveal{ opacity:1 !important; transform:none !important; } }

/* ============================================================
   B2B page blocks (migrated from the b2b.html <style> block)
   ============================================================ */
.b2b-badge{ display:inline-flex; align-items:center; gap:9px; font-size:13px; font-weight:600;
  letter-spacing:.14em; text-transform:uppercase; color:#fff; background:rgba(var(--scrim-tint),.9);
  padding:7px 16px; border-radius:999px; }
.b2b-badge .pulse{ width:8px; height:8px; border-radius:50%; background:#fff; position:relative; }
.b2b-badge .pulse::after{ content:""; position:absolute; inset:-4px; border-radius:50%;
  border:2px solid rgba(255,255,255,.6); animation:b2bpulse 1.8s ease-out infinite; }
@keyframes b2bpulse{ 0%{ transform:scale(.6); opacity:1; } 100%{ transform:scale(1.9); opacity:0; } }

/* capabilities */
.cap-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:1px; background:var(--line);
  border:1px solid var(--line); border-radius:20px; overflow:hidden; }
@media (max-width:860px){ .cap-grid{ grid-template-columns:1fr 1fr; } }
@media (max-width:520px){ .cap-grid{ grid-template-columns:1fr; } }
.cap{ background:var(--bg); padding:38px 32px 42px; transition:background .3s ease; }
.cap:hover{ background:#fff; }
.cap-ico{ width:46px; height:46px; display:flex; align-items:center; justify-content:center;
  border-radius:12px; background:rgba(var(--red-rgb),.08); color:var(--red); margin-bottom:22px; }
.cap-ico svg{ width:24px; height:24px; }
.cap h3{ font-size:22px; letter-spacing:-.01em; }
.cap p{ margin-top:12px; font-size:15px; color:var(--muted); line-height:1.55; }

/* steps */
.step-grid{ display:grid; grid-template-columns:repeat(4,1fr); gap:clamp(20px,3vw,36px); }
@media (max-width:820px){ .step-grid{ grid-template-columns:1fr 1fr; gap:36px 28px; } }
@media (max-width:460px){ .step-grid{ grid-template-columns:1fr; } }
.step .sn{ font-family:var(--display); font-weight:700; font-size:20px; color:var(--red);
  width:48px; height:48px; border-radius:50%; border:2px solid var(--red);
  display:flex; align-items:center; justify-content:center; }
.step h3{ font-size:20px; margin-top:22px; }
.step p{ margin-top:10px; font-size:15px; color:var(--muted); line-height:1.5; }
.dark .step p{ color:var(--muted-on-dark); }
.dark .step .sn{ color:var(--accent-bright); border-color:var(--accent-bright); }

/* audiences */
.aud-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:20px; }
@media (max-width:820px){ .aud-grid{ grid-template-columns:1fr; } }
.aud{ border:1.5px solid var(--line); border-radius:18px; padding:34px 30px; background:#fff; }
.aud .k{ font-size:13px; font-weight:600; letter-spacing:.14em; text-transform:uppercase; color:var(--red); }
.aud h3{ font-size:22px; margin-top:14px; }
.aud p{ margin-top:12px; font-size:15px; color:var(--muted); line-height:1.55; }

/* coming-soon modal */
.modal-overlay{ position:fixed; inset:0; z-index:2000; display:flex; align-items:center;
  justify-content:center; padding:24px; background:rgba(var(--deep-rgb),.72); backdrop-filter:blur(8px);
  opacity:0; visibility:hidden; transition:opacity .35s ease, visibility .35s ease; }
.modal-overlay.open{ opacity:1; visibility:visible; }
.modal{ position:relative; width:100%; max-width:520px; background:var(--panel); color:var(--text-on-dark);
  border:1px solid rgba(255,255,255,.1); border-radius:24px; overflow:hidden;
  box-shadow:0 40px 120px rgba(0,0,0,.55);
  transform:translateY(20px) scale(.97); transition:transform .4s cubic-bezier(.16,.84,.34,1); }
.modal-overlay.open .modal{ transform:none; }
.modal-visual{ position:relative; height:150px; overflow:hidden;
  background:radial-gradient(circle at 72% 120%, rgba(var(--red-rgb),.5) 0%, rgba(var(--red-rgb),0) 55%), #0F0C0A; }
.modal-visual .m-rings{ position:absolute; left:72%; top:120%;
  background:repeating-radial-gradient(circle, transparent 0 30px, rgba(var(--accent-bright-rgb),.22) 30px 31px);
  inset:0; -webkit-mask-image:radial-gradient(circle at 72% 120%, #000 0%, transparent 62%);
  mask-image:radial-gradient(circle at 72% 120%, #000 0%, transparent 62%); }
.modal-visual .m-node{ position:absolute; left:72%; top:120%; width:20px; height:20px;
  transform:translate(-50%,-50%); border-radius:50%; background:var(--accent-bright);
  box-shadow:0 0 40px 10px rgba(var(--accent-bright-rgb),.6); }
.modal-body{ padding:34px 38px 38px; }
.modal-badge{ display:inline-flex; align-items:center; gap:9px; font-size:12px; font-weight:600;
  letter-spacing:.16em; text-transform:uppercase; color:var(--accent-bright); }
.modal h2{ font-size:30px; margin-top:16px; color:#fff; letter-spacing:-.02em; }
.modal .m-lead{ margin-top:14px; color:var(--muted-on-dark); font-size:16px; line-height:1.55; }
.modal-form{ display:flex; gap:10px; margin-top:26px; }
.modal-form input{ flex:1; font-family:var(--body); font-size:15px; color:#fff;
  background:rgba(255,255,255,.06); border:1.5px solid rgba(255,255,255,.16); border-radius:12px;
  padding:14px 16px; transition:border-color .18s ease, box-shadow .18s ease; }
.modal-form input::placeholder{ color:var(--faint-on-dark); }
.modal-form input:focus{ outline:none; border-color:var(--accent-bright);
  box-shadow:0 0 0 3px rgba(var(--accent-bright-rgb),.18); }
.modal-form button{ white-space:nowrap; }
@media (max-width:480px){ .modal-form{ flex-direction:column; } .modal-form button{ justify-content:center; } }
.modal-note{ margin-top:16px; font-size:13px; color:var(--faint-on-dark); }
.modal-success{ display:none; margin-top:22px; padding:20px 22px; border-radius:14px;
  background:rgba(var(--accent-bright-rgb),.1); border:1px solid rgba(var(--accent-bright-rgb),.28); }
.modal-success.show{ display:block; }
.modal-success h3{ font-size:18px; color:#fff; } .modal-success p{ margin-top:6px; color:var(--soft-on-dark); }
/* 44px, not 38: this is the primary way out of a full-screen overlay on a
   phone, so it should not be the smallest target on the page. */
.modal-close{ position:absolute; top:14px; right:14px; z-index:3; width:44px; height:44px;
  border-radius:50%; border:1.5px solid rgba(255,255,255,.24); background:rgba(0,0,0,.25);
  color:#fff; font-size:18px; cursor:pointer; display:flex; align-items:center; justify-content:center;
  transition:background .2s ease, transform .2s ease; }
.modal-close:hover{ background:var(--red); border-color:var(--red); transform:rotate(90deg); }
.notify-inline .modal-success{ background:rgba(var(--red-rgb),.06); border-color:rgba(var(--red-rgb),.24); }
.notify-inline .modal-success h3{ color:var(--ink); } .notify-inline .modal-success p{ color:var(--muted); }

/* ---------- form error banner (server-side no-JS validation fallback) ---------- */
.form-alert{ margin:0 0 26px; padding:14px 18px; border-radius:12px; font-size:15px; line-height:1.5;
  background:rgba(var(--red-rgb),.07); border:1.5px solid rgba(var(--red-rgb),.35); color:var(--red-deep); }
.form-alert--dark{ background:rgba(var(--accent-bright-rgb),.12); border-color:rgba(var(--accent-bright-rgb),.4);
  color:#FFD9D5; margin-bottom:18px; }

/* ---------- per-section override hooks ----------
   The Phase 4 attribute editor writes whitelisted CSS variables into a
   section's style_overrides (see Validator::STYLE_WHITELIST). Each rule
   below only matches when its variable is actually present in the inline
   style, so unstyled sections render byte-identically. */
[style*="--section-bg:"]{ background:var(--section-bg); }
[style*="--section-text:"]{ color:var(--section-text); }
[style*="--eyebrow-color:"] .eyebrow{ color:var(--eyebrow-color); }
[style*="--h1-size:"] h1{ font-size:var(--h1-size); }
[style*="--h2-size:"] h2{ font-size:var(--h2-size); }
[style*="--h3-size:"] h3{ font-size:var(--h3-size); }
[style*="--lead-size:"] .lead{ font-size:var(--lead-size); }
[style*="--body-size:"]{ font-size:var(--body-size); }
[style*="--heading-weight:"] h1, [style*="--heading-weight:"] h2, [style*="--heading-weight:"] h3{ font-weight:var(--heading-weight); }
[style*="--body-weight:"] p{ font-weight:var(--body-weight); }
[style*="--section-pad-y:"]{ padding-block:var(--section-pad-y); }
/* --red, --accent-bright and --radius override the tokens directly and
   cascade through every rule that consumes them — no hook needed. */
