/* ===================================================================
   azizbelhadj.pro — design system
   Dark base, single orange accent, scroll-driven motion.
   Source of truth for tokens: this :root block. Do not hardcode colors.
   =================================================================== */

:root{
  /* base */
  --bg:        #0b0b0c;
  --bg-2:      #131315;
  --bg-3:      #1a1a1d;
  --ink:       #f5f4f1;
  --ink-2:     #a8a6a0;
  --ink-3:     #87847d; /* was #6e6c67 (~3.75:1 on --bg, fails WCAG AA); raised to ~5.27:1, still visibly quieter than --ink-2 (~8.08:1) */
  --line:      rgba(245,244,241,.11);
  --line-2:    rgba(245,244,241,.05);

  /* accent */
  --accent:      #FF5A1F;
  --accent-hot:  #FF7A3D;
  --accent-deep: #E8410A;
  --accent-soft: rgba(255,90,31,.16);
  --accent-glow: rgba(255,90,31,.45);

  --ease: cubic-bezier(.2,.9,.2,1);
  --ease-curtain: cubic-bezier(.7,.05,.2,1);
  --gutter: clamp(1.25rem, 4vw, 4rem);
  --r-card: 20px; --r-chip: 10px; --r-input: 12px; --r-pill: 999px;
  --grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 .32 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='.5'/></svg>");
}

/* ── reset / base ─────────────────────────────────────────────────── */
*{ box-sizing:border-box; }
/* font-size and overflow-x were silently inherited from OceanWP's stylesheet: with it
   loaded the computed root was 10px and html clipped overflow. Dequeuing OceanWP on
   Elementor views exposed that: the root jumped to the 16px browser default, scaling every
   rem in this file by 1.6x, and un-clipped horizontal overflow on mobile service pages.
   Declared here so the design owns its own base. Same computed values OceanWP produced, so
   this is a no-op on 404/search/archive routes where OceanWP is still loaded. */
html{ background:var(--bg); font-size:62.5%; overflow-x:hidden; }
body.ab-page{ margin:0; background:var(--bg); color:var(--ink);
  font-family:"Onest",system-ui,-apple-system,sans-serif; overflow-x:hidden; -webkit-font-smoothing:antialiased;
  /* Was inherited from OceanWP (body{overflow-wrap:break-word}). Without it the large
     display headings stop breaking mid-word: on a 390px viewport the service-page h1
     renders a single 513px word inside a 310px column, which html{overflow-x:hidden}
     then silently CLIPS rather than wraps. Must stay for the hero headings to fit. */
  overflow-wrap:break-word; word-wrap:break-word; }
.ab-page ::selection{ background:var(--accent); color:#0b0b0c; }
.ab-page a{ color:inherit; text-decoration:none; }
.ab-page img{ max-width:100%; display:block; }
.ab-page em, .ab-page .serif{ font-family:"Instrument Serif",Georgia,serif; font-style:italic; font-weight:400; color:var(--accent); }
/* Termly-embedded Privacy Policy: dozens of legitimate <em> emphasis tags in the
   legal text collide with the accent-serif em treatment above, which is meant for one
   deliberate headline moment, not body copy. Reset em back to plain emphasis here. */
.page-id-2833 em{ font-family:inherit; font-style:italic; font-weight:inherit; color:inherit; }
.ab-page button{ font-family:inherit; }

/* screen-reader only */
.ab-sr{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip:rect(0,0,0,0); white-space:nowrap; border:0; }
.ab-skip:focus{ position:fixed; top:1rem; left:1rem; width:auto; height:auto; clip:auto; z-index:100001;
  background:var(--accent); color:#0b0b0c; padding:.8rem 1.4rem; border-radius:var(--r-pill); font-weight:700; }

/* ── grain ────────────────────────────────────────────────────────── */
body.ab-page::before{
  content:""; position:fixed; inset:0; pointer-events:none; z-index:9000;
  background-image:var(--grain); opacity:.055; mix-blend-mode:screen;
}

/* ── custom cursor ────────────────────────────────────────────────── */
html.cur-on, html.cur-on *{ cursor:none !important; }
.cur{ position:fixed; top:0; left:0; z-index:100000; pointer-events:none; will-change:transform;
  opacity:0; transition:opacity .25s var(--ease); }
html.cur-on .cur{ opacity:1; }
.cur-ic{ position:absolute; top:0; left:0; display:block;
  filter: drop-shadow(0 0 1.4px rgba(0,0,0,.9)) drop-shadow(0 0 3px rgba(0,0,0,.5)); }
.cur-frame{ width:34px; height:34px; margin:-17px 0 0 -17px; opacity:1; transform:scale(1);
  transition:opacity .2s var(--ease), transform .32s var(--ease); }
.cur-frame .fr{ stroke:var(--ink); stroke-width:1.4; fill:none; }
.cur-frame .dot{ fill:var(--accent); }
.cur-hand{ width:44px; margin:-4px 0 0 -6px; opacity:0; transform:scale(.4); transform-origin:16px 2px;
  transition:opacity .2s var(--ease), transform .32s var(--ease); }
.cur.is-hand .cur-frame{ opacity:0; transform:scale(.45); }
.cur.is-hand .cur-hand{ opacity:1; transform:scale(1); }
.cur-view{ position:absolute; top:0; left:0; width:96px; height:96px; margin:-48px 0 0 -48px; border-radius:50%;
  background:var(--accent); box-shadow:0 12px 34px -8px var(--accent-glow);
  display:flex; align-items:center; justify-content:center; opacity:0; transform:scale(.4);
  transition:opacity .28s var(--ease), transform .38s var(--ease); }
.cur-view-ring{ position:absolute; inset:0; width:100%; height:100%; animation:curViewSpin 9s linear infinite; }
.cur-view-ring text{ font-family:"Onest",sans-serif; font-size:9.5px; font-weight:700; letter-spacing:1.7px; fill:#0b0b0c; }
.cur-view-arrow{ position:relative; width:22px; height:22px; }
.cur.is-view .cur-view{ opacity:1; transform:scale(1); }
.cur.is-view .cur-frame, .cur.is-view .cur-hand{ opacity:0; }
.cur.is-down .cur-frame{ transform:scale(.8); }
.cur.is-down.is-hand .cur-hand{ transform:scale(.88); }
.cur.is-down.is-view .cur-view{ transform:scale(.9); }
.cur-ring{ position:absolute; top:0; left:0; width:18px; height:18px; margin:-9px 0 0 -9px; border-radius:50%;
  border:2px solid var(--accent); transform:scale(0); opacity:0; }
.cur.is-click .cur-ring{ animation:curRipple .55s var(--ease); }
@keyframes curRipple{ 0%{transform:scale(.2);opacity:.9} 100%{transform:scale(3.4);opacity:0} }
@keyframes curViewSpin{ to{ transform:rotate(360deg); } }

/* ── load curtain ─────────────────────────────────────────────────── */
.curtain{ position:fixed; inset:0; background:var(--bg); z-index:9500; transform-origin:center top;
  transition:transform 1.1s var(--ease-curtain) .2s; display:flex; align-items:center; justify-content:center; }
.curtain.gone{ transform:scaleY(0); }
.curtain .cw{ font-size:clamp(11.2px,1.4vw,13.12px); letter-spacing:.34em; text-transform:uppercase; color:var(--ink-3); overflow:hidden; }
.curtain .cw span{ display:inline-block; animation:cwUp .9s var(--ease) forwards; opacity:0; transform:translateY(105%); }
@keyframes cwUp{ to{ opacity:1; transform:none; } }

/* ── timeline nav ─────────────────────────────────────────────────── */
.tl-host{ position:fixed; top:0; left:0; width:100%; z-index:300; pointer-events:none;
  padding:1.15rem var(--gutter) 1.9rem; display:flex; align-items:center; gap:2rem; }

/* The header floats over the page with no background, which is right over a
   full bleed hero and unreadable everywhere else: the wordmark and the
   timeline labels land on whatever happens to scroll under them.
   So the scrim appears only once the page has moved, leaving the top of every
   page exactly as designed.

   Masked rather than given a gradient background, because backdrop-filter
   blurs the whole element no matter what the background does. Fading the mask
   fades the blur and the tint together, so there is no hard edge where the
   blur stops. z-index keeps it behind the wordmark and the nav, both of which
   are unpositioned children of this stacking context. */
.tl-host::before{
  content:''; position:absolute; inset:0; z-index:-1; pointer-events:none;
  background:rgba(11,11,12,.78);
  backdrop-filter:blur(16px) saturate(1.25);
  -webkit-backdrop-filter:blur(16px) saturate(1.25);
  /* Solid past the timeline labels, which sit at 69% to 86% of the header
     box. Fading earlier left them on bare page: white labels over a white
     catalog spread. The extra padding-bottom above buys the room to fade out
     below them instead of through them. */
  -webkit-mask-image:linear-gradient(to bottom, #000 76%, transparent);
  mask-image:linear-gradient(to bottom, #000 76%, transparent);
  opacity:0; transition:opacity .3s var(--ease); }
.tl-host.is-stuck::before{ opacity:1; }
/* Without backdrop-filter the tint is doing the work alone, so it has to be
   heavier or the text underneath still reads through it. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .tl-host::before{ background:rgba(11,11,12,.92); } }
@media (prefers-reduced-motion: reduce){
  .tl-host::before{ transition:none; } }
.tl-mark{ pointer-events:auto; font-weight:700; letter-spacing:-.01em; font-size:clamp(15.2px,1vw,18.4px); flex:none; line-height:1.1; }
.tl-mark small{ display:block; font-weight:400; font-size:clamp(9.92px,.55vw,11.52px); letter-spacing:.2em; text-transform:uppercase; color:var(--ink-3); margin-top:.22rem; }
.tl-wrap{ position:relative; flex:1; max-width:760px; margin-left:auto; }
.tl-svg{ display:block; width:100%; height:54px; overflow:visible; }
.tl-track{ stroke:var(--line); stroke-width:1.2; fill:none; }
.tl-render{ stroke:var(--accent); stroke-width:2; fill:none; filter:drop-shadow(0 0 5px var(--accent-glow)); }
.kf{ fill:var(--ink-3); transition:fill .3s var(--ease); }
.kf.is-active{ fill:var(--accent); }
.tl-head{ fill:var(--accent); }
.tl-labels{ pointer-events:auto; display:flex; justify-content:space-between; margin-top:.1rem; }
.tl-label{ font-size:clamp(10.08px,.6vw,12.48px); letter-spacing:.09em; text-transform:uppercase; color:var(--ink-3);
  transition:color .3s var(--ease); display:flex; gap:.34rem; align-items:baseline; }
.tl-label .num{ font-variant-numeric:tabular-nums; font-weight:700; }
.tl-label:hover, .tl-label.is-active{ color:var(--ink); }
.tl-label.is-active .num{ color:var(--accent); }
@media (max-width:900px){ .tl-wrap{ display:none; } }

/* ── reveal ───────────────────────────────────────────────────────── */
.reveal{ opacity:0; transform:translateY(38px); filter:blur(14px);
  transition:opacity 820ms var(--ease), transform 980ms var(--ease), filter 760ms ease;
  will-change:opacity,transform,filter; }
.reveal.in{ opacity:1; transform:none; filter:blur(0); }
.reveal.d1{transition-delay:.08s} .reveal.d2{transition-delay:.16s}
.reveal.d3{transition-delay:.24s} .reveal.d4{transition-delay:.32s}
.no-js .reveal{ opacity:1!important; transform:none!important; filter:none!important; }
/* Elementor's own editor preview iframe is a different JS execution context
   than a real page load; if main.js's GSAP/ScrollTrigger/cursor init ever
   fails to run there, every .reveal element is permanently stuck at
   opacity:0 -- an entire Elementor-built page reads as solid black in the
   editor canvas, with no way to click or edit anything. Elementor tags
   <body> with elementor-editor-active inside that iframe specifically, so
   this is scoped to the editor only and never touches a real visitor. */
body.elementor-editor-active .reveal{ opacity:1!important; transform:none!important; filter:none!important; }

.ab-page section{ padding:clamp(4.5rem,10vw,8rem) var(--gutter); position:relative; }

/* ── hero ─────────────────────────────────────────────────────────── */
#hero{ min-height:100svh; display:flex; flex-direction:column; justify-content:center; padding-top:9rem; position:relative; overflow:hidden; }
.hero-glow{ position:absolute; width:60vw; height:60vw; max-width:820px; max-height:820px; right:-14vw; top:-10vw;
  background:radial-gradient(circle, var(--accent-soft) 0%, transparent 66%); filter:blur(46px); pointer-events:none;
  animation:glowDrift 22s ease-in-out infinite alternate; }
@keyframes glowDrift{ to{ transform:translate3d(-5vw,5vw,0) scale(1.12); } }
.eyebrow{ display:inline-flex; align-items:center; gap:.6rem; font-size:11.2px; letter-spacing:.22em;
  text-transform:uppercase; color:var(--ink-2); margin-bottom:1.6rem; }
.eyebrow .live{ width:7px; height:7px; border-radius:50%; background:var(--accent); position:relative; flex:none; }
.eyebrow .live::after{ content:""; position:absolute; inset:-4px; border-radius:50%; background:var(--accent); opacity:.5; animation:livepulse 2.2s ease-out infinite; }
@keyframes livepulse{ 0%{transform:scale(.5);opacity:.55} 100%{transform:scale(2.4);opacity:0} }
#hero h1{ font-size:clamp(40px,7.4vw,96px); font-weight:700; letter-spacing:-.035em; line-height:.98; margin:0 0 1.6rem; max-width:15ch; }
#hero h1 em{ font-size:1.04em; letter-spacing:-.01em; }
.lede{ color:var(--ink-2); font-size:clamp(16.32px,1.55vw,20.48px); line-height:1.55; max-width:52ch; margin:0 0 2.6rem; }
.lede b{ color:var(--ink); font-weight:500; }
.cta-row{ display:flex; align-items:center; gap:1.6rem; flex-wrap:wrap; }
.cta{ display:inline-flex; align-items:center; gap:.7rem; padding:1.05rem 2.1rem; border-radius:var(--r-pill);
  background:var(--accent); color:#0b0b0c; font-weight:700; font-size:15.2px;
  transition:transform .3s var(--ease), box-shadow .3s var(--ease), background .3s var(--ease); }
.cta:hover{ transform:translateY(-3px); background:var(--accent-hot); box-shadow:0 16px 38px -12px var(--accent-glow); }
.cta .arw{ transition:transform .3s var(--ease); }
.cta:hover .arw{ transform:translateX(4px); }
.cta-ghost{ font-size:14.4px; color:var(--ink-2); border-bottom:1px solid var(--line); padding-bottom:.2rem;
  transition:color .3s var(--ease), border-color .3s var(--ease); }
.cta-ghost:hover{ color:var(--ink); border-color:var(--accent); }

/* client marquee */
.clients{ margin-top:auto; padding-top:clamp(3rem,7vw,5rem); }
.clients .cap{ font-size:10.08px; letter-spacing:.2em; text-transform:uppercase; color:var(--ink-3); margin-bottom:1.1rem; }
.marquee{ border-top:1px solid var(--line); border-bottom:1px solid var(--line); overflow:hidden; padding:1rem 0;
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent);
          mask-image:linear-gradient(90deg,transparent,#000 8%,#000 92%,transparent); }
.marquee .track{ display:flex; gap:2.6rem; white-space:nowrap; animation:scrollx 42s linear infinite; width:max-content; }
.marquee:hover .track{ animation-play-state:paused; }
.marquee .track span{ display:inline-flex; align-items:center; gap:2.6rem; font-size:13.76px;
  letter-spacing:.11em; text-transform:uppercase; color:var(--ink-2); }
.marquee .track span::after{ content:"\25C6"; color:var(--accent); font-size:6px; }
/* -25%, not -50%: the track now renders 4 passes of the client list (see
   ab-marquee's render()), not 2 -- a short list doubled to 2 passes left
   almost no scroll buffer on wide viewports (1920px+), where the visible
   container ends up nearly as wide as the whole doubled track, reading as
   "stuck" rather than looping. -25% moves exactly one of the 4 copies'
   width, same seamless-wrap math, just with 2x the buffer at any width. */
@keyframes scrollx{ to{ transform:translateX(-25%); } }

/* ── service page ─────────────────────────────────────────────────── */
#intro{ padding-top:9rem; position:relative; overflow:hidden; }
#intro h1{ font-size:clamp(35.2px,6vw,73.6px); font-weight:700; letter-spacing:-.033em;
  line-height:1.02; margin:0 0 1.5rem; max-width:17ch; }
.crumb{ display:flex; align-items:center; gap:.6rem; font-size:10.88px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--ink-3); margin-bottom:1.5rem; }
.crumb a{ transition:color .3s var(--ease); }
.crumb a:hover{ color:var(--accent); }
.status-line{ display:flex; align-items:center; gap:.55rem; font-size:13.6px; color:var(--ink-2); }
.status-line .live{ width:7px; height:7px; border-radius:50%; background:var(--accent); position:relative; flex:none; }
.status-line .live::after{ content:""; position:absolute; inset:-4px; border-radius:50%;
  background:var(--accent); opacity:.5; animation:livepulse 2.2s ease-out infinite; }
.prose{ color:var(--ink-2); font-size:clamp(16px,1.4vw,18.24px); line-height:1.72; max-width:62ch; margin:0; }

/* about */
.about-grid{ display:grid; grid-template-columns:1.35fr .65fr; gap:clamp(2rem,5vw,4rem); align-items:start; }
.about-grid .prose p{ margin:0 0 1.3rem; }
.about-grid .prose p:last-child{ margin-bottom:0; }
.facts{ border-left:1px solid var(--line); padding-left:clamp(1.4rem,3vw,2.2rem); }
.fact{ padding-bottom:1.6rem; }
.fact:last-child{ padding-bottom:0; }
.fact .fv{ display:block; font-size:clamp(27.2px,3.4vw,40px); font-weight:700;
  letter-spacing:-.04em; line-height:1; color:var(--accent); font-variant-numeric:tabular-nums; }
.fact .fl{ display:block; margin-top:.45rem; color:var(--ink-2); font-size:13.76px; line-height:1.45; max-width:24ch; }
@media (max-width:860px){
  .about-grid{ grid-template-columns:1fr; }
  .facts{ border-left:0; border-top:1px solid var(--line); padding-left:0; padding-top:1.8rem;
    display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:1.4rem; }
  .fact{ padding-bottom:0; }
}

.other-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:.8rem; }
.other-card{ display:flex; align-items:center; justify-content:space-between; gap:1rem;
  border:1px solid var(--line); border-radius:var(--r-card); padding:1.35rem 1.5rem;
  transition:border-color .35s var(--ease), background .35s var(--ease); }
.other-card:hover{ border-color:var(--accent); background:var(--accent-soft); }
.other-card .ol{ font-weight:600; font-size:16px; letter-spacing:-.012em; }
.other-card .oa{ color:var(--accent); transition:transform .3s var(--ease); }
.other-card:hover .oa{ transform:translateX(4px); }

/* ── section heads ────────────────────────────────────────────────── */
.sec-head{ margin-bottom:clamp(2.6rem,5vw,4rem); }
.sec-idx{ display:flex; align-items:baseline; gap:.7rem; margin-bottom:1rem; }
.sec-idx .n{ font-size:11.52px; font-weight:700; letter-spacing:.14em; color:var(--accent); font-variant-numeric:tabular-nums; }
.sec-idx .rule{ height:1px; background:var(--line); flex:1; max-width:120px; }
.sec-idx .of{ font-size:10.08px; letter-spacing:.16em; text-transform:uppercase; color:var(--ink-3); }
.sec-head h2{ font-size:clamp(29.6px,4.2vw,49.6px); font-weight:700; letter-spacing:-.028em; line-height:1.06; margin:0; max-width:19ch; }
.sec-head p{ color:var(--ink-2); max-width:56ch; margin:1.1rem 0 0; font-size:16.32px; line-height:1.6; }

/* ── work ─────────────────────────────────────────────────────────── */
.filters{ display:flex; gap:.55rem; margin-bottom:2.4rem; flex-wrap:wrap; }
.filters button{ background:none; border:1px solid var(--line); color:var(--ink-3);
  padding:.55rem 1.2rem; border-radius:var(--r-pill); font-size:12.48px; letter-spacing:.05em; transition:all .3s var(--ease); }
.filters button:hover{ color:var(--ink); border-color:var(--ink-3); }
.filters button.active{ background:var(--accent); border-color:var(--accent); color:#0b0b0c; font-weight:700; }
.filters .count{ font-variant-numeric:tabular-nums; opacity:.6; margin-left:.35rem; }

.case{ display:grid; grid-template-columns:1.15fr .85fr; gap:clamp(1.6rem,4vw,3.2rem);
  border-top:1px solid var(--line); padding:clamp(2rem,4vw,3.2rem) 0; }
.case.hidden{ display:none; }
.case .shot{ position:relative; border-radius:var(--r-card); overflow:hidden; aspect-ratio:16/10; background:var(--bg-2); }
.case .shot img{ width:100%; height:100%; object-fit:cover;
  transition:transform 1.2s var(--ease), filter .6s var(--ease); filter:saturate(.82); }
.case:hover .shot img{ transform:scale(1.045); filter:saturate(1); }
.case .stamp{ position:absolute; top:1rem; left:1rem; background:rgba(11,11,12,.82); backdrop-filter:blur(9px);
  border:1px solid var(--line); border-radius:var(--r-pill); padding:.38rem .82rem; font-size:10.56px;
  letter-spacing:.1em; text-transform:uppercase; color:var(--ink-2); }
.case .stamp.ai{ border-color:var(--accent); color:var(--accent); }
.case .meta{ display:flex; align-items:baseline; gap:.7rem; margin-bottom:.5rem; flex-wrap:wrap; }
.case .cat{ font-size:10.56px; font-weight:700; letter-spacing:.13em; text-transform:uppercase; color:var(--accent); }
.case .yr{ font-size:10.56px; color:var(--ink-3); font-variant-numeric:tabular-nums; }
.case h3{ font-size:clamp(20.8px,2.3vw,28px); font-weight:700; letter-spacing:-.02em; margin:0 0 1.3rem; }
.case h3 a:hover{ color:var(--accent); }
.fld{ margin-bottom:1.1rem; }
.fld .k{ font-size:10.08px; font-weight:700; letter-spacing:.13em; text-transform:uppercase; color:var(--ink-3); display:block; margin-bottom:.32rem; }
.fld p{ margin:0; color:var(--ink-2); font-size:15.04px; line-height:1.55; }
.fld.out p{ color:var(--ink); }
.work-empty{ border-top:1px solid var(--line); padding:2.5rem 0; color:var(--ink-3); }

/* Card text column: anchor content to the top and pin the CTA to the bottom so
   short entries do not leave a large void beside a tall image. */
.case > div:last-child{ display:flex; flex-direction:column; }
.case-go{ margin-top:auto; padding-top:1.2rem; font-size:11.52px; letter-spacing:.12em;
  text-transform:uppercase; color:var(--ink-3); transition:color .3s var(--ease); }
.case-go span{ display:inline-block; transition:transform .3s var(--ease); }
.case:hover .case-go{ color:var(--accent); }
.case:hover .case-go span{ transform:translateX(4px); }

/* ── services ─────────────────────────────────────────────────────── */
.svc{ position:relative; z-index:0; border-top:1px solid var(--line); padding:clamp(1.6rem,3vw,2.2rem) 0;
  display:grid; grid-template-columns:3.4ch 1fr auto; gap:clamp(1rem,3vw,2.2rem); align-items:start;
  transition:padding-left .45s var(--ease); color:inherit; }
/* Homepage renders these as links; keep the hover affordance readable. */
a.svc:hover{ padding-left:.75rem; }
a.svc:hover h3{ color:var(--accent); }
.svc h3{ transition:color .3s var(--ease); }
.svc .go span{ display:inline-block; transition:transform .3s var(--ease); }
a.svc:hover .go span{ transform:translateX(4px); }
/* True full-bleed highlight: a separate layer broken out to the real
   viewport edge (left:50%+margin-left:-50vw, not width:100% -- that would
   only fill .svc's own padded row), sitting behind the row's real content
   via a negative z-index rather than the background living on .svc itself,
   which used to cap it to the padded column same as .pf-preview did. */
.svc::before{ content:''; position:absolute; top:0; bottom:0; left:50%; width:100vw; margin-left:-50vw;
  z-index:-1; opacity:0; transition:opacity .45s var(--ease); pointer-events:none;
  background:linear-gradient(90deg, var(--accent-soft), transparent 62%), var(--accent-soft); background-blend-mode:normal; }
.svc:hover::before{ opacity:1; }
.svc .n{ color:var(--accent); font-weight:700; font-size:12.48px; font-variant-numeric:tabular-nums; padding-top:.35rem; }
.svc h3{ margin:0 0 .5rem; font-size:clamp(17.6px,2vw,22.4px); font-weight:600; letter-spacing:-.015em; }
.svc p{ color:var(--ink-2); margin:0 0 .85rem; font-size:15.04px; line-height:1.58; max-width:54ch; }
.chips{ display:flex; gap:.42rem; flex-wrap:wrap; }
.chips span{ font-size:10.88px; border:1px solid var(--line); padding:.3rem .7rem; border-radius:var(--r-chip); color:var(--ink-3); }
.svc .go{ font-size:11.52px; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-3);
  white-space:nowrap; padding-top:.4rem; transition:color .3s var(--ease); }
.svc:hover .go{ color:var(--accent); }

/* ── pipeline ─────────────────────────────────────────────────────── */
#pipeline{ background:var(--bg-2); }
.pipe{ display:grid; grid-template-columns:repeat(auto-fit,minmax(168px,1fr)); gap:1px;
  background:var(--line); border:1px solid var(--line); border-radius:var(--r-card); overflow:hidden; }
.pipe .st{ background:var(--bg-2); padding:clamp(1.4rem,2.6vw,2rem); transition:background .4s var(--ease); }
.pipe .st:hover{ background:var(--bg-3); }
.pipe .st .n{ font-size:10.88px; font-weight:700; letter-spacing:.14em; color:var(--ink-3); font-variant-numeric:tabular-nums; }
.pipe .st h3{ margin:.7rem 0 .5rem; font-size:16.32px; font-weight:600; letter-spacing:-.012em; }
.pipe .st p{ margin:0; color:var(--ink-2); font-size:13.92px; line-height:1.55; }
.pipe .st.key{ background:var(--accent); }
.pipe .st.key:hover{ background:var(--accent-hot); }
.pipe .st.key .n{ color:rgba(11,11,12,.62); }
.pipe .st.key h3{ color:#0b0b0c; }
.pipe .st.key p{ color:rgba(11,11,12,.82); }

/* ── proof ────────────────────────────────────────────────────────── */
.stats{ display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:clamp(1.6rem,4vw,3rem); }
.stat .v{ font-size:clamp(38.4px,5.6vw,67.2px); font-weight:700; letter-spacing:-.045em; line-height:1;
  color:var(--accent); font-variant-numeric:tabular-nums; }
.stat .l{ margin-top:.7rem; color:var(--ink-2); font-size:14.4px; line-height:1.5; max-width:26ch; }

/* ── process ──────────────────────────────────────────────────────── */
.rush{ margin:2.4rem 0 0; padding:1.1rem 1.3rem; border-left:2px solid var(--accent);
  background:var(--bg-2); border-radius:0 var(--r-chip) var(--r-chip) 0;
  color:var(--ink-2); font-size:14.4px; line-height:1.6; max-width:64ch; }
.tline{ position:relative; padding-left:3.2rem; }
.tline::before{ content:""; position:absolute; left:9px; top:8px; bottom:8px; width:1px; background:var(--line); }
.tline .fill{ position:absolute; left:9px; top:8px; width:1px; background:var(--accent); box-shadow:0 0 9px var(--accent-glow); height:0; }
.ph{ position:relative; padding-bottom:clamp(2rem,4vw,2.9rem); }
.ph:last-child{ padding-bottom:0; }
.ph::before{ content:""; position:absolute; left:-3.2rem; top:4px; width:19px; height:19px; border-radius:50%;
  background:var(--bg); border:1px solid var(--line); transition:border-color .4s var(--ease); }
.ph::after{ content:""; position:absolute; left:calc(-3.2rem + 6px); top:11px; width:7px; height:7px; border-radius:50%;
  background:var(--ink-3); transition:background .4s var(--ease), box-shadow .4s var(--ease); }
.ph.on::before{ border-color:var(--accent); }
.ph.on::after{ background:var(--accent); box-shadow:0 0 10px var(--accent-glow); }
.ph .wk{ font-size:10.4px; letter-spacing:.16em; text-transform:uppercase; color:var(--accent); }
.ph h3{ margin:.45rem 0 .5rem; font-size:19.2px; font-weight:600; letter-spacing:-.015em; }
.ph p{ color:var(--ink-2); margin:0 0 .8rem; max-width:52ch; font-size:15.04px; line-height:1.58; }

/* ── why ──────────────────────────────────────────────────────────── */
.why{ display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:clamp(1.8rem,4vw,3rem); }
.why .r .n{ color:var(--accent); font-weight:700; font-size:11.52px; letter-spacing:.14em; font-variant-numeric:tabular-nums; }
.why .r h3{ margin:.6rem 0 .55rem; font-size:17.6px; font-weight:600; letter-spacing:-.015em; }
.why .r p{ color:var(--ink-2); font-size:14.72px; margin:0; line-height:1.58; }

/* ── faq ──────────────────────────────────────────────────────────── */
.ab-page details{ border-bottom:1px solid var(--line); padding:1.35rem 0; }
.ab-page summary{ font-weight:600; font-size:16.64px; list-style:none; display:flex;
  justify-content:space-between; gap:2rem; align-items:center; transition:color .3s var(--ease); }
.ab-page summary:hover{ color:var(--accent); }
.ab-page summary::-webkit-details-marker{ display:none; }
.ab-page summary .pm{ color:var(--accent); font-size:21.6px; line-height:1; transition:transform .35s var(--ease); flex:none; }
.ab-page details[open] summary .pm{ transform:rotate(45deg); }
.ab-page details p{ color:var(--ink-2); margin:1rem 0 0; max-width:62ch; line-height:1.62; font-size:15.2px; }

/* ── case study ───────────────────────────────────────────────────── */
.cs #intro h1{ font-size:clamp(32px,5.2vw,64px); font-weight:700; letter-spacing:-.032em;
  line-height:1.04; margin:0 0 1.4rem; max-width:20ch; }
.cs-meta{ display:flex; align-items:baseline; gap:.9rem; margin-bottom:1.1rem; flex-wrap:wrap; }
.cs-meta .cat{ font-size:10.88px; font-weight:700; letter-spacing:.14em; text-transform:uppercase; color:var(--accent); }
.cs-meta .yr{ font-size:10.88px; color:var(--ink-3); font-variant-numeric:tabular-nums; }
.cs #intro .chips{ margin-top:1.6rem; }

/* ── case study hero ──────────────────────────────────────────────────
   Full bleed featured image with the hero copy laid over it. The plate is
   an <img> rather than a background-image so it keeps srcset and alt; it is
   pinned behind the copy with a scrim between the two.

   Copy sits at the bottom of the frame. The fixed site header floats over
   the top of the plate, which is why the scrim is opaque at both ends and
   thin through the middle: the top band protects the nav, the bottom band
   protects the headline, and the picture stays visible between them. */
.cs-intro{ position:relative; overflow:hidden; }
.cs-intro.has-media{
  min-height:min(92svh,860px);
  display:flex; flex-direction:column; justify-content:flex-end;
  padding-top:clamp(9rem,18vh,14rem);
  padding-bottom:clamp(3rem,7vh,5.5rem);
}
.cs-hero-img{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; object-position:center;
  z-index:0; border-radius:0;
}
.cs-hero-scrim{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(to bottom,
      rgba(11,11,12,.78) 0%,
      rgba(11,11,12,.34) 22%,
      rgba(11,11,12,.30) 46%,
      rgba(11,11,12,.80) 78%,
      var(--bg) 100%),
    linear-gradient(to right,
      rgba(11,11,12,.62) 0%,
      rgba(11,11,12,.20) 52%,
      rgba(11,11,12,0) 100%);
}
.cs-intro-inner{ position:relative; z-index:2; width:100%; }
.cs-intro.has-media .cs-intro-inner{ max-width:62rem; }
/* Over a photo the headline needs a touch more weight than it does on flat bg. */
.cs-intro.has-media h1{ text-shadow:0 2px 30px rgba(11,11,12,.55); }
.cs-intro.has-media .lede{ color:#d9d7d2; text-shadow:0 1px 18px rgba(11,11,12,.6); }

.cs-disclose{ margin:1.8rem 0 0; font-size:12.16px; color:var(--ink-3); letter-spacing:.02em; }
.cs-intro.has-media .cs-disclose{ color:#96938d; }

/* Hero, related work and the contact block run edge to edge. The story
   between them stays boxed: those three are display, but the story is a read,
   and a read wants edges to sit inside rather than the whole screen. */
.cs #intro, .cs #related, .cs #contact{ max-width:none; width:100%; }
.cs #story{ max-width:1400px; margin-inline:auto; width:100%; }
.cs #intro > *{ max-width:none; }

@media (max-width:700px){
  .cs-intro.has-media{ min-height:min(82svh,680px); }
  .cs-hero-scrim{
    background:linear-gradient(to bottom,
      rgba(11,11,12,.72) 0%,
      rgba(11,11,12,.40) 30%,
      rgba(11,11,12,.86) 74%,
      var(--bg) 100%);
  }
}

/* Body prose. Post content is clean semantic HTML, so it is styled by element
   rather than by class; nothing here depends on editor-specific markup.

   Text holds a readable measure and centres; images and embeds break wider.
   That contrast is what gives a long case study rhythm instead of one narrow
   ribbon of content down the page. */
/* Sizes below are px, not rem: Elementor's own global reset sets html{font-size:10px}
   sitewide (confirmed on every ported page, not something this build introduced), so
   these rules -- authored pre-Elementor against a standard 16px root -- rendered at
   ~62.5% of their intended size (body text as small as 11px) until converted. */
.cs-body{ max-width:760px; margin-inline:auto; }
.cs-body > *{ margin:0 0 1.35rem; }
.cs-body > *:last-child{ margin-bottom:0; }
.cs-body p{ color:var(--ink-2); font-size:clamp(18px,1.35vw,20px); line-height:1.75; }
.cs-body strong{ color:var(--ink); font-weight:600; }
.cs-body h2{ color:var(--ink); font-size:clamp(28px,3.4vw,40px); font-weight:700;
  letter-spacing:-.024em; line-height:1.14; margin:clamp(2.8rem,5vw,4rem) 0 1.1rem; max-width:22ch; }
.cs-body h2:first-child{ margin-top:0; }
.cs-body h3{ color:var(--ink); font-size:22px; font-weight:600; margin:2.2rem 0 .8rem; }
.cs-body a{ color:var(--accent); border-bottom:1px solid var(--accent-soft); transition:border-color .3s var(--ease); }
.cs-body a:hover{ border-bottom-color:var(--accent); }
.cs-body ul, .cs-body ol{ padding-left:1.3rem; color:var(--ink-2); }
.cs-body li{ margin-bottom:.6rem; line-height:1.7; font-size:clamp(18px,1.3vw,19px); }
.cs-body li::marker{ color:var(--accent); }
.cs-body blockquote{ margin:clamp(2rem,4vw,3rem) 0; padding:0 0 0 1.6rem;
  border-left:2px solid var(--accent); }
.cs-body blockquote p{ color:var(--ink); font-family:"Instrument Serif",Georgia,serif;
  font-style:italic; font-size:clamp(22px,2.4vw,28px); line-height:1.42; }
.cs-body blockquote p:last-child{ margin-bottom:0; }

/* Images break out past the text measure so they read as plates rather than
   inline figures. Centred on the wider container, not the text column. */
.cs-body img{ width:100%; height:auto; border-radius:var(--r-card); display:block; }
.cs-body p:has(> img),
.cs-body > figure,
.cs-body iframe,
.cs-body > video,
.cs-body .cs-reels{
  width:min(100vw - (var(--gutter) * 2), 1180px);
  max-width:none;
  margin-left:50%;
  transform:translateX(-50%);
}
.cs-body p:has(> img){ margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem); }
.cs-body > figure{ margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem); }
.cs-body figcaption{ margin-top:.8rem; font-size:12.8px; color:var(--ink-3); text-align:center; }

/* iframes carry no intrinsic size, so they need a ratio. Video files do carry
   one, and forcing 16/9 on them letterboxed every vertical social cut on the
   site. Let the file decide, and cap the height so a portrait clip cannot run
   taller than the screen. */
.cs-body iframe{ aspect-ratio:16/9; height:auto; border:0; border-radius:var(--r-card);
  margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem); }
.cs-body video{ display:block; height:auto; max-width:100%; border-radius:var(--r-card); background:#000; }
.cs-body > video{ max-height:82vh; margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem); }
/* A <video> written by hand carries no width attribute, so it falls back to the
   browser's 300x150 default and renders as a small player floating at the left
   edge of an otherwise full width plate. Fill the plate. Height still comes
   from the file's own ratio, capped so a portrait clip cannot run off screen;
   contain over a black background means that cap letterboxes invisibly. */
.cs-body > figure > video{ width:100%; max-height:82vh; object-fit:contain; }
/* WordPress's own video shortcode sets fixed width/height attributes. */
.cs-body .wp-video, .cs-body .wp-video-shortcode, .cs-body .mejs-container{
  width:100% !important; height:auto !important; max-width:100%; }

/* Client logos are stored as ordinary images, so without this the case study
   image rule blows a 150px mark up to full plate width. They read as a row of
   marks, muted until hovered so they support the copy rather than shout. */
/* Breaks out past the 70ch text measure. At prose width five marks wrap onto
   two ragged rows, which reads as a layout accident rather than a credential
   row. Narrower than the 1180px image plate so the marks stay grouped instead
   of stranded across the full width. */
.cs-logos{ display:flex; flex-wrap:wrap; align-items:center; justify-content:flex-start;
  gap:clamp(1.4rem,3.5vw,2.8rem); margin:clamp(2rem,4vw,3rem) 0;
  padding:clamp(1.2rem,2.5vw,1.8rem) 0; border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  width:min(100vw - (var(--gutter) * 2), 860px); max-width:none;
  margin-left:50%; transform:translateX(-50%); }
.cs-logos img{ width:auto; height:clamp(30px,4.5vw,44px); margin:0; border-radius:0;
  object-fit:contain; opacity:.55; filter:grayscale(1) brightness(1.8);
  transition:opacity .35s var(--ease), filter .35s var(--ease); }
.cs-logos img:hover{ opacity:1; filter:grayscale(0) brightness(1); }
/* A lone tagged logo still should not become a full width plate. */
.cs-body img[data-logo]{ width:auto; max-width:200px; height:auto; border-radius:0; }

/* Strip: a short row of stills placed deliberately in the body, as opposed to
   .cs-gallery which is the rescue for entries whose images never made it into
   post_content. Holds portrait 3:4 because these are usually behind the scenes
   frames shot on a phone, and cropping them to landscape throws away the part
   that shows the room. */
.cs-strip{
  width:min(100vw - (var(--gutter) * 2), 1180px); max-width:none;
  margin-left:50%; transform:translateX(-50%);
  margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem);
  display:grid; grid-template-columns:repeat(3,1fr); gap:clamp(.6rem,1.4vw,1rem); }
.cs-strip figure{ margin:0; }
.cs-strip img{ width:100%; aspect-ratio:3/4; object-fit:cover; border-radius:var(--r-card);
  filter:saturate(.9); transition:filter .5s var(--ease); }
.cs-strip figure:hover img{ filter:saturate(1); }
/* A landscape frame among portraits takes the whole row at its own ratio.
   Spanning only part of a row would leave it short against the tall frames
   beside it, with a gap under it that reads as a broken grid. */
.cs-strip figure.is-wide{ grid-column:1 / -1; }
.cs-strip figure.is-wide img{ aspect-ratio:16/9; }
/* Two up on small screens. Nothing is dropped: an odd last frame simply spans
   the row rather than sitting in a half empty one. */
/* Frames shot on a phone are 9:16 natively. Cropping them to the strip's 3:4
   would be cropping the format the work was composed in, on a page whose whole
   argument is that the format was not a limitation. Four across, because these
   are meant to be read as a body of work rather than examined one by one. */
.cs-strip.is-tall{ grid-template-columns:repeat(4,1fr); }
.cs-strip.is-tall img{ aspect-ratio:9/16; filter:none; }
/* Six across where the set is large enough that the body of work is the point
   and any single frame is not. */
.cs-strip.is-tall.is-dense{ grid-template-columns:repeat(6,1fr); }
/* Three across where the frames carry fine detail a thumbnail would lose, a
   drop forming on a dropper tip or oil on a fingertip. Stacked full width they
   ran 2100px each and turned six frames into a scroll nobody finishes. */
.cs-strip.is-tall.is-3{ grid-template-columns:repeat(3,1fr); }
@media (max-width:720px){
  .cs-strip{ grid-template-columns:1fr 1fr; }
  .cs-strip.is-tall{ grid-template-columns:1fr 1fr; }
  .cs-strip.is-tall.is-dense{ grid-template-columns:repeat(3,1fr); }
  .cs-strip.is-tall.is-3{ grid-template-columns:1fr 1fr; }
  .cs-strip figure:last-child:nth-child(odd){ grid-column:1 / -1; }
  .cs-strip figure:last-child:nth-child(odd) img{ aspect-ratio:16/9; }
  .cs-strip.is-tall figure:last-child:nth-child(odd){ grid-column:auto; }
  .cs-strip.is-tall figure:last-child:nth-child(odd) img{ aspect-ratio:9/16; } }

/* Scale figures. Some projects are countable and the number is the argument:
   fifteen books, nine hundred photographs. The previous build had these as
   Elementor counter widgets, which stored 0 as the start value and animated
   up on scroll. With that JS gone the live page reads "Catalogs 0 Photos 0 +",
   which is the whole reason this component exists.
   So the finished number is what sits in the markup. The count up is
   decoration applied on top of an already correct value, and with no JS, or
   with reduced motion, the reader still sees 15 and 900+. */
.cs-stats{
  width:min(100vw - (var(--gutter) * 2), 1180px); max-width:none;
  margin-left:50%; transform:translateX(-50%);
  margin-top:clamp(2.4rem,5vw,4rem); margin-bottom:clamp(2.4rem,5vw,4rem);
  display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
  gap:clamp(1.2rem,3vw,2.4rem);
  border-top:1px solid var(--line-2); border-bottom:1px solid var(--line-2);
  padding:clamp(1.8rem,4vw,3rem) 0; }
.cs-stats div{ margin:0; }
.cs-stats .n{ display:block; font-family:"Instrument Serif",Georgia,serif;
  font-size:clamp(51.2px,7.5vw,89.6px); line-height:.95; letter-spacing:-.01em;
  font-variant-numeric:tabular-nums; color:var(--accent); }
.cs-stats .l{ display:block; margin-top:.7rem; font-size:10.88px;
  letter-spacing:.18em; text-transform:uppercase; color:var(--ink-3); }
@media (max-width:560px){ .cs-stats{ grid-template-columns:1fr 1fr; } }

/* Product range. Denser than .cs-strip on purpose: the claim is that one
   treatment held across every artisan and material, and a claim about a range
   is only cashed by seeing the range at once. Square because that is how the
   shots were delivered and how the catalog grid uses them. */
.cs-products{
  width:min(100vw - (var(--gutter) * 2), 1180px); max-width:none;
  margin-left:50%; transform:translateX(-50%);
  margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem);
  display:grid; grid-template-columns:repeat(6,1fr); gap:clamp(.35rem,.9vw,.65rem); }
.cs-products figure{ margin:0; }
.cs-products img{ width:100%; aspect-ratio:1/1; object-fit:cover;
  border-radius:var(--r-chip); }
/* Column count follows what the row is for. Six is right for scanning a whole
   range at once. It is wrong when the reader has to actually examine each
   frame: a group shot of twenty products at 185px is a smudge, and putting one
   there contradicts the sentence it is meant to prove. */
.cs-products.is-4{ grid-template-columns:repeat(4,1fr); }
.cs-products.is-2{ grid-template-columns:repeat(2,1fr); }
@media (max-width:900px){
  .cs-products{ grid-template-columns:repeat(4,1fr); }
  .cs-products.is-4{ grid-template-columns:repeat(3,1fr); } }
@media (max-width:560px){
  .cs-products{ grid-template-columns:repeat(3,1fr); }
  .cs-products.is-4{ grid-template-columns:repeat(2,1fr); }
  .cs-products.is-2{ grid-template-columns:1fr; } }

/* A run of full width images at their own ratio, tightly stacked. Written for
   catalog spreads read in page order, and used since for any set that belongs
   together and must not be cropped to a shared ratio: campaign frames carry
   brand lockups and legal marks in the corners, so forcing them into 16/9
   would cut off the part that is legally required to be there.
   Tighter gaps than a sequence of loose figures, so consecutive images read as
   one run rather than as unrelated pictures that happen to follow each other. */
.cs-pages{
  width:min(100vw - (var(--gutter) * 2), 1180px); max-width:none;
  margin-left:50%; transform:translateX(-50%);
  margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem);
  display:grid; gap:clamp(.45rem,1.1vw,.85rem); }
.cs-pages figure{ margin:0; }
.cs-pages img{ width:100%; border-radius:var(--r-chip); }

/* Ambient video: a decorative clip that plays itself, muted and looping, with
   no player chrome. Used where footage sets a mood rather than making an
   argument the reader has to watch.

   WCAG 2.2.2: anything that moves for more than five seconds needs a way to
   stop it, so main.js adds a real toggle button rather than leaving the only
   control on a click handler no keyboard can reach. The video itself ignores
   pointer events so the button owns every interaction.

   Under prefers-reduced-motion main.js never starts it, restores the native
   controls and leaves the poster showing, so the clip stays reachable without
   moving on its own. */
.cs-amb{ position:relative; }
.cs-amb video{ pointer-events:none; }
.cs-amb-toggle{
  position:absolute; right:clamp(.7rem,1.4vw,1.1rem); bottom:clamp(.7rem,1.4vw,1.1rem);
  display:inline-flex; align-items:center; gap:.4rem; cursor:pointer;
  font-family:inherit; font-size:10.08px; letter-spacing:.11em; text-transform:uppercase;
  color:var(--ink); background:rgba(11,11,12,.62); border:1px solid var(--line);
  padding:.5rem .85rem; border-radius:var(--r-pill);
  opacity:.5; transition:opacity .3s var(--ease), background-color .3s var(--ease); }
.cs-amb:hover .cs-amb-toggle, .cs-amb-toggle:focus-visible{ opacity:1; background:rgba(11,11,12,.85); }
.cs-amb-toggle:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }
/* Paused is a state the reader chose, so it stays visible rather than fading. */
.cs-amb.is-paused .cs-amb-toggle{ opacity:1; }
/* Coarse pointers get no hover, so the control cannot hide behind one. */
@media (pointer: coarse){ .cs-amb-toggle{ opacity:1; } }

/* Hand-written embeds.
   ab_repair_media() inserts its iframes at priority 20, after wpautop, so they
   land as direct children of .cs-body and the breakout rule above reaches them.
   An iframe written into post_content by hand is inline level, so wpautop wraps
   it in a <p> first. That happens to centre correctly today only because the
   prose column is centred; wrap it in .cs-embed instead so the breakout belongs
   to a block element and does not depend on that. The inner iframe must then
   drop its own breakout or it offsets twice. */
.cs-body .cs-embed{
  width:min(100vw - (var(--gutter) * 2), 1180px); max-width:none;
  margin-left:50%; transform:translateX(-50%);
  margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem); }
.cs-body .cs-embed iframe{ width:100%; margin:0; transform:none; }

/* Before and after pair: a design and the thing that got built from it.
   Deliberately a two up rather than a wipe slider. The two frames are a render
   and a photograph, not two exposures of one shot, so a slider would promise a
   pixel alignment that does not exist and the seam would read as a mistake.
   Side by side, the eye does the comparison itself.

   Both images are pre cropped to 16:9 before upload so the pair is like for
   like; object-fit only guards against a future image that is not.
   Numbered captions borrow the timeline nav's language (accent numeral, small
   caps label) so the comparison reads as part of the page, not a widget. */
.cs-ba{
  width:min(100vw - (var(--gutter) * 2), 1180px); max-width:none;
  margin-left:50%; transform:translateX(-50%);
  margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem);
  display:grid; grid-template-columns:1fr 1fr; gap:clamp(.6rem,1.4vw,1rem); }
.cs-ba figure{ margin:0; }
.cs-ba img{ width:100%; aspect-ratio:16/9; object-fit:cover; border-radius:var(--r-card); }
.cs-ba figcaption{ margin-top:.75rem; text-align:left; display:flex; gap:.4rem; align-items:baseline;
  font-size:10.56px; letter-spacing:.11em; text-transform:uppercase; color:var(--ink-3); }
.cs-ba figcaption .n{ font-variant-numeric:tabular-nums; font-weight:700; color:var(--accent); }
/* Stacked, the two frames still read in order, so the comparison survives. */
@media (max-width:720px){ .cs-ba{ grid-template-columns:1fr; gap:1.4rem; } }

/* Vertical social cuts, shown as a reel row rather than a stack of full width
   portrait players each taller than the viewport. */
.cs-reels{ display:flex; gap:clamp(.7rem,1.6vw,1.1rem); overflow-x:auto; overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory; padding-bottom:.9rem;
  margin-top:clamp(2rem,4vw,3.2rem); margin-bottom:clamp(2rem,4vw,3.2rem);
  scrollbar-width:thin; scrollbar-color:var(--accent) transparent; }
.cs-reels::-webkit-scrollbar{ height:6px; }
.cs-reels::-webkit-scrollbar-thumb{ background:var(--accent); border-radius:99px; }
.cs-reels::-webkit-scrollbar-track{ background:var(--line-2); border-radius:99px; }
.cs-reels figure{ margin:0; flex:0 0 auto; width:clamp(190px,24vw,280px); scroll-snap-align:start; }
.cs-reels video{ width:100%; aspect-ratio:9/16; object-fit:cover; border-radius:var(--r-chip); }

/* The gallery lives inside the boxed story, so it holds the same plate width
   as the images that break out of the prose. Related work is outside the box
   and runs the full section width. */
.cs-gallery{ max-width:1180px; margin-inline:auto; }
.cs #related > .reveal, .cs #related .sec-head{ max-width:none; }

/* Recovered gallery for entries whose images never lived in the body. */
.cs-gallery{ display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
  gap:clamp(.8rem,2vw,1.4rem); margin-top:clamp(2.5rem,5vw,4rem); }
.cs-gallery figure{ margin:0; border-radius:var(--r-card); overflow:hidden; background:var(--bg-2); }
.cs-gallery img{ width:100%; height:100%; aspect-ratio:4/3; object-fit:cover; display:block;
  filter:saturate(.9); transition:transform 1s var(--ease), filter .5s var(--ease); }
.cs-gallery figure:hover img{ transform:scale(1.04); filter:saturate(1); }

/* ── site nav ─────────────────────────────────────────────────────── */
.site-nav{ pointer-events:auto; display:flex; align-items:center; gap:1.35rem; flex:none; padding-top:.15rem; }
.site-nav a{ font-size:clamp(11.84px,.7vw,14.08px); letter-spacing:.13em; text-transform:uppercase; color:var(--ink-3);
  transition:color .3s var(--ease); white-space:nowrap; }
.site-nav a:hover, .site-nav a.is-here{ color:var(--ink); }
.site-nav a.is-here{ position:relative; }
.site-nav a.is-here::after{ content:""; position:absolute; left:0; right:0; bottom:-5px; height:1px; background:var(--accent); }
.site-nav a:last-child{ color:var(--accent); }
@media (max-width:620px){ .site-nav a:not(:last-child){ display:none; } }

/* ── show more ────────────────────────────────────────────────────── */
.more-btn{ display:inline-flex; align-items:center; gap:.6rem; margin-top:2rem;
  background:none; border:1px solid var(--line); color:var(--ink-2);
  padding:.85rem 1.7rem; border-radius:var(--r-pill); font-size:13.12px; letter-spacing:.06em;
  transition:all .3s var(--ease); }
.more-btn:hover{ border-color:var(--accent); color:var(--ink); background:var(--accent-soft); }
.more-btn .mi{ color:var(--accent); font-size:17.6px; line-height:1; transition:transform .35s var(--ease); }
.more-btn.is-open .mi{ transform:rotate(45deg); }
.case[hidden]{ display:none; }

/* ── contact / footer ─────────────────────────────────────────────── */
#contact{ background:var(--bg-2); }
.ctc h2{ font-size:clamp(33.6px,6vw,70.4px); font-weight:700; letter-spacing:-.035em; line-height:1.02; margin:0 0 1.5rem; max-width:16ch; }
.ctc .sub{ color:var(--ink-2); font-size:16.8px; max-width:48ch; margin:0 0 2.4rem; line-height:1.6; }

.ab-footer{ border-top:1px solid var(--line); padding:clamp(2.6rem,5vw,4rem) var(--gutter) 1.6rem; }
.ft-top{ display:grid; grid-template-columns:1.6fr 1fr 1fr 1.2fr; gap:clamp(1.8rem,4vw,3.5rem); margin-bottom:clamp(2.4rem,5vw,3.5rem); }
.ft-brand .ft-mark{ font-weight:700; font-size:clamp(16.8px,1.1vw,20.8px); letter-spacing:-.015em; display:block; margin-bottom:.85rem; }
.ft-line{ color:var(--ink-3); font-size:clamp(13.76px,.8vw,16px); line-height:1.6; margin:0 0 1.35rem; }
/* Same story as .ft-h: an <a>, so `.elementor-kit-34 a { font-weight:700 }`
   outranked the 600 asked for here, and `a:hover { font-weight:900 }` made it
   reflow on hover. The hover restates the weight so only the arrow moves. */
.ab-footer .ft-cta{ display:inline-flex; align-items:center; gap:.5rem; color:var(--accent); font-size:clamp(13.76px,.8vw,16px); font-weight:600; }
.ab-footer .ft-cta:hover{ font-weight:600; }
.ft-cta span{ display:inline-block; transition:transform .3s var(--ease); }
.ft-cta:hover span{ transform:translateX(4px); }
.ft-col{ display:flex; flex-direction:column; gap:.7rem; }
/* Prefixed with .ab-footer for specificity, not for scoping. These are <h2>s,
   and `.elementor-kit-34 h2 { font-size:3rem; font-weight:700; color:primary }`
   is (0,1,1) against a bare class's (0,1,0) -- so the kit was winning and the
   column labels rendered as 30px bold white headlines instead of the small
   letterspaced markers written here. Two classes settles it. */
.ab-footer .ft-h{ font-size:clamp(10.08px,.6vw,12.16px); letter-spacing:.2em; text-transform:uppercase; color:var(--ink-3); margin:0 0 .35rem; font-weight:600; }
/* Weight is stated here on purpose. The Elementor kit styles bare tags --
   `.elementor-kit-34 a{ font-weight:700 }` and `a:hover{ font-weight:900 }` --
   which this rule only ties on specificity and wins on load order, so leaving
   it out means the footer menu inherits the kit's bold and, worse, jumps two
   weights on hover and reflows the whole column. 500 reads as navigation
   beside the 400 of .ft-line without shouting; the hover repeats it so the
   only thing that changes on hover is colour. */
.ft-col a{ color:var(--ink-2); font-size:clamp(13.92px,.8vw,16px); font-weight:500; transition:color .3s var(--ease); }
.ft-col a:hover{ color:var(--accent); font-weight:500; }
.ft-base{ border-top:1px solid var(--line-2); padding-top:1.5rem; display:flex;
  justify-content:space-between; align-items:center; flex-wrap:wrap; gap:1rem; }
.ab-footer .cp{ color:var(--ink-3); font-size:clamp(12.8px,.75vw,15.2px); }
/* Privacy Policy is an `a.cp` sitting between two `span.cp`, so it took the
   kit's bold and was the only one of the three that looked like a heading. It
   now matches its neighbours. The weight jump was also its only hover feedback,
   so it gets the colour shift the rest of the footer links use instead. */
.ab-footer a.cp{ font-weight:400; transition:color .3s var(--ease); }
.ab-footer a.cp:hover{ font-weight:400; color:var(--ink-2); }
@media (max-width:860px){ .ft-top{ grid-template-columns:1fr 1fr; } }
@media (max-width:520px){ .ft-top{ grid-template-columns:1fr; } }

/* ── responsive ───────────────────────────────────────────────────── */
@media (max-width:860px){
  .case, .svc{ grid-template-columns:1fr; }
  .svc .go{ display:none; }
  #hero{ min-height:auto; padding-top:7.5rem; }
}

/* ── reduced motion ───────────────────────────────────────────────── */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.01ms!important; animation-iteration-count:1!important; transition-duration:.01ms!important; }
  .reveal{ opacity:1; transform:none; filter:none; }
  html.cur-on, html.cur-on *{ cursor:auto!important; }
  .cur{ display:none; }
  .curtain{ display:none; }
}

/* ── admin bar offset ─────────────────────────────────────────────── */
body.admin-bar .tl-host{ top:32px; }
body.admin-bar .curtain{ top:32px; }
@media screen and (max-width:782px){
  body.admin-bar .tl-host{ top:46px; }
  body.admin-bar .curtain{ top:46px; }
}

/* ── 404 page ─────────────────────────────────────────────────────────
   There is no 404.php in this child theme, so OceanWP's own template
   renders with OceanWP's colours on top of the site's dark background.
   Two of those combinations are real WCAG failures (both flagged by
   Lighthouse, both confirmed on the live page):

     search input  #333333 on #0b0b0c = 1.55:1  (typed text near-invisible)
     "Back To Homepage"  #f5f4f1 on #13aff0 = 2.26:1  (OceanWP default blue)

   Scoped to .error404-content so nothing here can leak into the search
   form or buttons used elsewhere on the site. Colours are the existing
   brand tokens already used by the ab-button widget, not new ones. */
.error404-content .searchform .field{
  background:#131315;
  color:#f5f4f1;
  border:1px solid rgba(245,244,241,.25);
  border-radius:999px;
  padding:.9rem 1.4rem;
}
.error404-content .searchform .field::placeholder{
  color:rgba(245,244,241,.55);  /* 5.4:1 on #131315 */
  opacity:1;                    /* Firefox dims placeholders by default */
}
.error404-content .searchform .field:focus{
  outline:2px solid #FF5A1F;
  outline-offset:2px;
  border-color:rgba(245,244,241,.45);
}
.error404-content .error-btn.button{
  background:#FF5A1F;           /* brand orange, same as ab-button solid */
  color:#0b0b0c;                /* dark on orange = 6.31:1 */
  border:none;
  border-radius:999px;
  padding:1.05rem 2.1rem;
  font-weight:600;
  text-decoration:none;
  display:inline-flex;
  align-items:center;
  transition:background-color .3s ease, transform .3s ease;
}
.error404-content .error-btn.button:hover,
.error404-content .error-btn.button:focus{
  background:#FF7A3D;
  color:#0b0b0c;
  transform:translateY(-3px);
}
