/* LYNQ — site styles
   White ground, black type, green accent blocks. Structure follows the measured
   platter.com teardown (references/platter-study/06-platter-site-teardown.md):
   near-white page, one restrained type scale, full-pill buttons, accent used as
   BLOCKS and chips rather than as the main button. Brand fonts stay ours. */

@font-face{
  font-family:"Poppins";
  src:url("../fonts/Poppins-ExtraBold.woff2") format("woff2"),
      url("../fonts/Poppins-ExtraBold.ttf") format("truetype");
  font-weight:800; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"DM Sans";
  src:url("../fonts/DMSans-Variable.woff2") format("woff2-variations"),
      url("../fonts/DMSans-Variable.ttf") format("truetype-variations");
  font-weight:100 1000; font-style:normal; font-display:swap;
}

:root{
  --paper:#ffffff;
  --paper-alt:#f7f7f7;
  --ink:#111111;
  --ink-soft:#4a4a48;
  --muted:#6b6b67;
  --green:#c1ff72;
  --danger:#b3261e;         /* 6.4:1 on white, so error text is readable, not just red */
  --line:rgba(17,17,17,.11);
  --line-strong:rgba(17,17,17,.22);

  --display:"Poppins","Segoe UI",system-ui,sans-serif;
  --body:"DM Sans","Segoe UI",system-ui,sans-serif;

  --r-card:16px;
  --r-pill:1440px;          /* platter's own value, a full pill at any height */
  --shell:1360px;
  --pad:clamp(20px,3vw,40px);
  --head-h:114px;      /* 22px top gap + 92px bar. Also the body offset. */
  --ease:cubic-bezier(.22,.61,.36,1);
}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth; -webkit-text-size-adjust:100%;
     scroll-padding-top:calc(var(--head-h) + env(safe-area-inset-top,0px) + 16px)}
body{
  /* viewport-fit=cover hands us the whole screen, notch included, so every
     edge inset has to be paid back by hand. In return the page scrolls under
     the status bar and Safari fades it there instead of cutting it off. */
  margin:0; background:var(--paper); color:var(--ink);
  padding-top:calc(var(--head-h) + env(safe-area-inset-top,0px));
  padding-left:env(safe-area-inset-left,0px); padding-right:env(safe-area-inset-right,0px);
  font-family:var(--body); font-size:16px; line-height:1.6; font-weight:400;
  -webkit-font-smoothing:antialiased;
}
img{max-width:100%; display:block}
a{color:inherit}
button{font:inherit; cursor:pointer}
.shell{width:min(100% - (var(--pad) * 2), var(--shell)); margin-inline:auto}

/* ── type ─────────────────────────────────────────────────────────
   platter measured: H1 48px/1.1 at desktop, 40px mobile, tracking -0.48px.
   Kept close, in Poppins ExtraBold rather than their Suisse. */
h1,h2,h3{font-family:var(--display); font-weight:800; letter-spacing:-.022em; line-height:1.08; margin:0}
h1{font-size:clamp(2.5rem, 4.2vw, 3.05rem)}
h2{font-size:clamp(1.9rem, 3vw, 2.3rem); line-height:1.14}
h3{font-size:1.08rem; letter-spacing:-.015em; line-height:1.25}
p{margin:0 0 1em}
.lede{font-size:1.02rem; line-height:1.62; color:var(--muted); max-width:58ch}
.eyebrow{
  font-size:.7rem; font-weight:700; letter-spacing:.15em; text-transform:uppercase;
  color:var(--muted); margin:0 0 16px;
}
.accent{color:var(--ink)}

/* The full stop after a section heading picks up the green, echoing the dot
   in the wordmark. Decorative punctuation only, so the 1.4:1 contrast of
   #c1ff72 carries no meaning that could be lost. */
.dot-accent{color:var(--ink)}


/* Accent used the way platter uses lime: as a highlight behind the words,
   not as coloured text, which would fail contrast at 1.4:1. */
.mark{
  background-image:linear-gradient(var(--green),var(--green));
  background-repeat:no-repeat;
  background-size:100% .16em;
  background-position:0 92%;
  padding-bottom:.06em;
}

/* ── buttons ──────────────────────────────────────────────────────
   platter: background #000, color #fff, radius 1440px, padding 12px 32px. */
.btn{
  display:inline-flex; align-items:center; gap:9px; justify-content:center;
  padding:13px 30px; border-radius:var(--r-pill); border:1px solid transparent;
  font-weight:600; font-size:.95rem; text-decoration:none; letter-spacing:-.005em;
  transition:transform .16s var(--ease), background-color .16s var(--ease),
             border-color .16s var(--ease), color .16s var(--ease);
}
.btn:hover{transform:translateY(-1px)}
.btn-primary{background:var(--ink); color:#fff; border-color:var(--ink)}
.btn-primary:hover{background:#000}
.btn-ghost{background:transparent; color:var(--ink); border-color:var(--line-strong)}
.btn-ghost:hover{border-color:var(--ink)}
.btn-green{background:var(--green); color:var(--ink); border-color:var(--green)}
.btn-green:hover{background:var(--green); border-color:var(--green)}
.btn-arrow{font-size:1.05em; line-height:1; transition:transform .16s var(--ease)}
.btn:hover .btn-arrow{transform:translateX(3px)}

/* ── header ───────────────────────────────────────────────────────
   platter shape: logo far left, nav immediately beside it, single pill CTA
   far right, hairline underneath. The CTA leaves the bar on mobile. */
/* width comes from --vw, set in JS from documentElement.clientWidth, NOT from
   right:0. A fixed element resolves left/right against the viewport while
   everything in <main> resolves against the body, and those two boxes differ
   by the scrollbar width in Chrome device mode. The bar then centres itself in
   the wider box: left edge lands on the content, right edge overhangs. platter
   has the same offset and gets away with it because their bar is transparent
   at rest, so there is no visible edge. Ours is a white card, so it shows. */
.site-head{position:fixed; top:0; left:0; right:auto; width:var(--vw,100%);
           z-index:60; background:transparent; pointer-events:none;
           padding-top:calc(22px + env(safe-area-inset-top,0px));
           padding-left:env(safe-area-inset-left,0px);
           padding-right:env(safe-area-inset-right,0px)}
.site-head .inner,.site-head .inner *{pointer-events:auto}
.site-head .inner{
  display:flex; align-items:center; gap:34px; position:relative;
  height:92px; padding:0 16px 0 30px;
  width:min(100% - (var(--pad) * 2), var(--shell));
  background:transparent; border:1px solid transparent; border-radius:22px;
  box-shadow:none;
  transition:height .34s var(--ease), background-color .28s var(--ease),
             border-color .28s var(--ease), box-shadow .28s var(--ease);
}
/* At rest the bar is part of the page: no ground, no hairline. The backdrop
   arrives on scroll, and previews on hover so the bar still feels alive at the top. */
.site-head:hover .inner,
.site-head.floating .inner{
  background:rgba(255,255,255,.86);
  backdrop-filter:saturate(1.7) blur(12px);
  border-color:var(--line);
  box-shadow:0 10px 34px rgba(17,17,17,.10);
}
.site-head.floating .inner{height:84px; background:rgba(255,255,255,.93)}
.brand{display:flex; align-items:center; flex:none; text-decoration:none}
.brand-icon{height:46px; width:auto; flex:none; transition:height .34s var(--ease)}
.site-head.floating .brand-icon{height:42px}

/* The wordmark is folded away behind the icon and unfurls when the bar is
   hovered, in either state. max-width drives the slide, so it needs a bound
   larger than the mark ever gets: at 26px tall it is about 58px wide. */
.brand-word{
  display:flex; align-items:center; overflow:hidden;
  max-width:0; opacity:0; margin-left:0;
  transition:max-width .42s var(--ease), opacity .28s var(--ease),
             margin-left .42s var(--ease);
}
.brand-word img{height:34px; width:auto; display:block; transition:height .34s var(--ease)}
.site-head.floating .brand-word img{height:31px}
.site-head:hover .brand-word,
.brand:focus-visible .brand-word{max-width:150px; opacity:1; margin-left:12px}

.site-nav{
  display:flex; gap:26px; align-items:center;
  position:absolute; left:0; right:0; margin-inline:auto; width:max-content;
}
.site-nav a{
  color:var(--ink); text-decoration:none; font-size:.95rem; font-weight:500;
  opacity:1; transition:opacity .15s;
}
.site-nav a:hover{opacity:.55}

/* ── the Diensten dropdown ─────────────────────────────────────────
   Shape taken from the platter Services menu he sent: a white rounded card
   under the bar, one row per page, each row an icon tile beside a title and a
   line of copy. The two icons are the ones already on the homepage service
   cards, the storefront for Bouwen and the rocket for Optimaliseren, so one
   offer carries one mark wherever it appears.

   Opening is CSS :hover on .nav-drop, not JavaScript. CSS :hover follows the
   DOM tree and not the box tree, so the pointer sitting on the absolutely
   positioned panel still counts as hovering its parent. JS only adds .open for
   click and Escape and keeps aria-expanded honest; it is not what reveals the
   panel, which is why the menu still works before site.js has run.

   visibility, not display or [hidden]: visibility:hidden takes the two links
   out of the tab order while closed, and unlike display it can be transitioned
   and can still be flipped back by a :hover rule. */
.nav-drop{position:relative; display:flex; align-items:center}
.nav-drop__btn{
  display:flex; align-items:center; gap:7px; cursor:pointer;
  font:inherit; font-size:.95rem; font-weight:500; color:var(--ink);
  background:transparent; border:0; border-radius:11px;
  padding:8px 13px; margin:0 -13px;      /* negative margin keeps the 26px nav gap even */
  transition:background-color .18s var(--ease);
}
.nav-drop__btn svg{width:11px; height:11px; flex:none; transition:transform .24s var(--ease)}
.nav-drop:hover .nav-drop__btn,
.nav-drop.open .nav-drop__btn{background:#efefef}
.nav-drop:hover .nav-drop__btn svg,
.nav-drop.open .nav-drop__btn svg{transform:rotate(180deg)}

.nav-drop__panel{
  position:absolute; top:calc(100% + 30px); left:-22px;
  width:400px; padding:12px;
  background:var(--paper); border:1px solid var(--line); border-radius:22px;
  box-shadow:0 22px 60px rgba(17,17,17,.16);
  display:grid; gap:2px;
  opacity:0; visibility:hidden; transform:translateY(-8px);
  transition:opacity .2s var(--ease), transform .2s var(--ease), visibility .2s;
}
/* The pointer has to cross the gap between the trigger and the card. Without
   this bridge the panel is unreachable on a mouse: hover drops the instant the
   cursor leaves the text. Invisible, and it covers only the gap. */
.nav-drop__panel::before{content:""; position:absolute; left:0; right:0; top:-30px; height:30px}
.nav-drop:hover .nav-drop__panel,
.nav-drop.open .nav-drop__panel{opacity:1; visibility:visible; transform:none}

.nav-drop__item{
  display:grid; grid-template-columns:auto 1fr; gap:14px; align-items:start;
  padding:14px; border-radius:16px; text-decoration:none;
  transition:background-color .16s var(--ease);
}
/* .site-nav a:hover fades every nav link to .55 and outscores a bare
   .nav-drop__item:hover, so this has to carry the .site-nav prefix too. */
.site-nav .nav-drop__item:hover{background:var(--paper-alt); opacity:1}
.nav-drop__ico{
  width:44px; height:44px; border-radius:13px; background:#f1f1f1;
  display:grid; place-items:center; color:var(--ink); flex:none;
}
.nav-drop__ico svg{width:21px; height:21px; display:block}
.nav-drop__t{
  display:block; font-family:var(--display); font-weight:800;
  font-size:1.02rem; letter-spacing:-.01em; color:var(--ink); margin-bottom:3px;
}
.nav-drop__d{display:block; font-size:.875rem; line-height:1.45; color:var(--muted)}
@media (prefers-reduced-motion:reduce){
  .nav-drop__panel,.nav-drop__btn svg,.nav-drop__item{transition:none}
}

.head-actions{display:flex; align-items:center; gap:12px; flex:none; margin-left:auto}
.site-head .btn{padding:10px 22px; font-size:.9rem}

.lang{display:flex; border:1px solid var(--line-strong); border-radius:var(--r-pill); overflow:hidden}
.lang button{
  background:none; border:0; color:var(--muted); padding:6px 12px;
  font-size:.75rem; font-weight:700; letter-spacing:.06em; transition:.15s;
}
.lang button[aria-pressed="true"]{background:var(--ink); color:#fff}

/* The bars fold into a cross. This was stripped out while the fixed-header
   glitch was being hunted; that turned out to be a DevTools artefact and not
   the animation, so the motion is back. 6px is the exact bar pitch: the spans
   sit at 5, 11 and 17 once their 4px margins collapse. */
.nav-toggle{display:none; background:none; border:0; padding:8px; color:var(--ink); flex:none}
.nav-toggle span{
  display:block; width:21px; height:2px; background:currentColor; margin:4px 0;
  transition:transform .34s var(--ease), opacity .18s var(--ease);
}
.nav-toggle[aria-expanded="true"] span:nth-child(1){transform:translateY(6px) rotate(45deg)}
.nav-toggle[aria-expanded="true"] span:nth-child(2){opacity:0; transform:scaleX(.4)}
.nav-toggle[aria-expanded="true"] span:nth-child(3){transform:translateY(-6px) rotate(-45deg)}

/* ── mobile nav panel ───────────────────────────────────────
   Shape measured off platter.com at 390px: inset card level with the bar,
   20px/500 links on a 16px gap, and a full width pill pinned under them.
   Theirs is a black pill, ours is the brand green, since this panel is the
   one place on the site where green can carry a button without sitting on
   white. Desktop never renders it. */
.nav-panel,.nav-scrim{display:none}

@media (max-width:820px){
  /* Invisible: it exists to catch a tap outside the panel, nothing else.
     Being fixed and transparent, a drag across it still scrolls the page. */
  .nav-scrim{display:block; position:fixed; inset:0; z-index:65; visibility:hidden}
  .nav-scrim.open{visibility:visible}

  .nav-panel{
    display:flex; flex-direction:column; position:fixed; z-index:70;
    top:calc(22px + env(safe-area-inset-top,0px));
    left:calc(var(--pad) + env(safe-area-inset-left,0px)); right:auto;
    width:calc(var(--vw,100%) - var(--pad) * 2
               - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px));
    max-height:calc(100dvh - 44px - env(safe-area-inset-top,0px)
                    - env(safe-area-inset-bottom,0px));
    overflow-y:auto; overscroll-behavior:contain;
    padding:0 18px; background:#fff;
    border:1px solid var(--line); border-radius:20px;
    box-shadow:0 20px 54px rgba(17,17,17,.18);
    /* The panel grows out of the bar rather than landing on top of it. Its
       head is exactly the bar height, so the first 70px revealed sit over the
       bar unchanged and the rest unfolds downward from underneath it.
       clip-path animates on the compositor, so nothing reflows on the way. */
    transform-origin:top center;
    opacity:0; visibility:hidden; transform:scale(.99);
    clip-path:inset(0 0 calc(100% - 70px) 0 round 20px);
    transition:opacity .16s var(--ease), clip-path .44s var(--ease),
               transform .44s var(--ease), visibility .44s;
  }
  .nav-panel.open{
    opacity:1; visibility:visible; transform:none;
    clip-path:inset(0 0 0 0 round 20px);
  }

  /* The contents ride in just behind the unfold, staggered, so the panel
     reads as one movement instead of a box that arrives already full. The
     delays live only on .open, so closing collapses everything at once. */
  /* Was scoped to `a`. The panel now also holds the Diensten group label, which
     is a <p>, and a bare `a` selector left it visible while its siblings were
     still staggering in. `> *` covers whatever the cloner emits. */
  .nav-panel-links > *,.nav-panel-cta{
    opacity:0; transform:translateY(10px);
    transition:opacity .28s var(--ease), transform .28s var(--ease);
  }
  .nav-panel.open .nav-panel-links > *,
  .nav-panel.open .nav-panel-cta{opacity:1; transform:none}
  .nav-panel.open .nav-panel-links > :nth-child(1){transition-delay:.15s}
  .nav-panel.open .nav-panel-links > :nth-child(2){transition-delay:.19s}
  .nav-panel.open .nav-panel-links > :nth-child(3){transition-delay:.23s}
  .nav-panel.open .nav-panel-links > :nth-child(4){transition-delay:.27s}
  .nav-panel.open .nav-panel-links > :nth-child(5){transition-delay:.31s}
  .nav-panel.open .nav-panel-links > :nth-child(6){transition-delay:.35s}
  .nav-panel.open .nav-panel-links > :nth-child(7){transition-delay:.39s}
  .nav-panel.open .nav-panel-links > :nth-child(8){transition-delay:.43s}
  .nav-panel.open .nav-panel-cta{transition-delay:.47s}

  /* 70px matches the bar height exactly, so the wordmark does not shift
     when the panel opens over it. */
  .nav-panel-head{display:flex; align-items:center; justify-content:space-between; height:70px; flex:none}
  .nav-close{background:none; border:0; padding:8px; margin-right:-6px; color:var(--ink); line-height:0}
  .nav-close svg{width:22px; height:22px; display:block}

  .nav-panel-links{display:flex; flex-direction:column; gap:16px; padding:14px 0 0}
  .nav-panel-links a{
    font-size:1.25rem; font-weight:500; letter-spacing:-.018em; line-height:1.2;
    color:var(--ink); text-decoration:none; padding:8px 0;
  }
  .nav-panel-links a:active{opacity:.55}

  /* Touch has no hover, so the dropdown cannot come across as one here. The two
     service pages are rendered as an indented, labelled group instead: same two
     destinations, no interaction to discover, nothing to tap open. */
  .nav-panel-group{
    margin:8px 0 -6px; font-size:.72rem; font-weight:700; letter-spacing:.12em;
    text-transform:uppercase; color:var(--muted);
  }
  .nav-panel-links a.nav-panel-sub{
    font-size:1.12rem; padding-left:16px; border-left:2px solid var(--line);
  }

  .nav-panel-cta{padding:44px 0 26px; margin-top:auto}
  .nav-panel-cta .btn{width:100%; padding:15px 32px; font-size:1rem}
}

/* ── sections ─────────────────────────────────────────────────────
   Two grounds only: white and #f7f7f7. Green appears as one full band. */
section{padding:clamp(64px,8vw,104px) 0}
.alt{background:var(--paper-alt)}
.sec-head{max-width:62ch; margin-bottom:clamp(36px,4.5vw,54px)}
.sec-wide{max-width:none}
@media (min-width:1100px){ .sec-wide h2{font-size:2.15rem; white-space:nowrap} }
.sec-head h2{margin-bottom:16px}

/* ── hero ─────────────────────────────────────────────────────────
   platter: hero 769px tall at 1440, CTAs above the fold, proof directly under. */
.hero{padding:clamp(48px,6vw,84px) 0 clamp(48px,6vw,72px)}
.hero-grid{display:grid; grid-template-columns:1fr 1.08fr; gap:clamp(32px,5vw,68px); align-items:center}
.hero-copy{min-width:0}

/* The pad colour is sampled from each screenshot dominant hue and set inline
   per frame as --pad, so adding a slide never means touching this file. */
.hero-visual{min-width:0}
.frame-stack{display:grid}
.frame{
  grid-area:1 / 1; margin:0; padding:clamp(16px,2.4vw,30px);
  background:var(--pad); border-radius:26px;
  display:flex; align-items:center; justify-content:center;
  opacity:0; visibility:hidden; transition:opacity .55s var(--ease), visibility .55s;
}
.frame.is-active{opacity:1; visibility:visible}
.frame .shot{
  width:100%; aspect-ratio:16 / 10.4; overflow:hidden; border-radius:12px;
  box-shadow:0 16px 38px rgba(17,17,17,.20);
}
.frame .shot img{width:100%; height:100%; object-fit:cover; object-position:top center}
.frame-dots{display:flex; gap:7px; justify-content:center; margin-top:22px}
.fdot{
  position:relative; width:44px; height:6px; border-radius:3px; border:0; padding:0;
  background:rgba(17,17,17,.16); overflow:hidden; cursor:pointer;
}
.fdot:hover{background:rgba(17,17,17,.30)}
.fdot::after{
  content:""; position:absolute; left:0; top:0; bottom:0; width:0;
  background:var(--ink); border-radius:3px;
}
@keyframes fdotFill{ from{width:0} to{width:100%} }
.fdot.is-on::after{animation:fdotFill var(--dwell,5s) linear forwards}
/* Hover pauses the slide, so the bar has to stop with it or it lies. */
.hero-visual.is-paused .fdot.is-on::after{animation-play-state:paused}
.hero h1{margin-bottom:22px; max-width:17ch}
.hero .lede{margin-bottom:32px; font-size:1.08rem; max-width:52ch}
.hero-cta{display:flex; gap:12px; flex-wrap:wrap; margin-bottom:0}

.hero-meta{
  display:flex; gap:0; flex-wrap:wrap; font-size:.88rem; color:var(--muted);
  margin-top:44px; padding-top:22px; border-top:1px solid var(--line);
}
.hero-meta span{display:flex; align-items:center; gap:8px; padding-right:30px}
.dot{width:6px; height:6px; border-radius:50%; background:var(--green); flex:none}

/* ── the green band ───────────────────────────────────────────────
   One accent block on the page, the way platter uses its lime CTA section. */
.offer{background:var(--green); color:var(--ink)}
.offer .eyebrow{color:var(--ink)}
.offer .lede{color:var(--ink)}
.offer-grid{display:grid; grid-template-columns:1.05fr .95fr; gap:clamp(30px,4.5vw,64px); align-items:center}
.offer h2{margin-bottom:16px}
.steps{list-style:none; margin:22px 0 0; padding:0; counter-reset:s}
.steps li{counter-increment:s; position:relative; padding:0 0 18px 48px; font-size:.98rem}
.steps li::before{
  content:counter(s); position:absolute; left:0; top:-1px;
  width:30px; height:30px; border-radius:50%; background:var(--ink); color:var(--green);
  display:grid; place-items:center; font-weight:700; font-size:.82rem; font-family:var(--display);
}
.offer-note{
  margin-top:24px; padding:16px 20px; border-radius:var(--r-card);
  background:rgba(255,255,255,.55); font-size:.93rem; color:var(--ink);
}
.offer-alt{margin:18px 0 0; font-size:.95rem; color:var(--ink)}
/* The link takes its own line. Letting it wrap mid-phrase under the question
   read as an accident rather than a second door. text-decoration underlines
   the glyphs, not the box, so display:block keeps the rule tight to the words. */
.offer-alt .ul-link{display:block; width:fit-content; margin-top:3px}

/* Underlined so it reads as clickable without being a second button. */
.ul-link{
  color:var(--ink); font-weight:600; text-decoration:underline;
  text-underline-offset:3px; text-decoration-thickness:2px;
}
.ul-link:hover{text-decoration-thickness:3px}

/* ── cards ────────────────────────────────────────────────────────
   platter's service cards: white, hairline, small lime icon chip. */
.cards{display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:18px}
/* Six cards under one heading. auto-fit fits five across a 1360 shell and leaves
   the sixth stranded on a row of its own, which reads as a broken grid rather
   than as a deliberate one. Three up is the only split of six that stays even at
   every breakpoint, so it is set explicitly rather than left to auto-fit. */
.cards--3{grid-template-columns:repeat(3,1fr)}
@media (max-width:900px){ .cards--3{grid-template-columns:repeat(2,1fr)} }
@media (max-width:560px){ .cards--3{grid-template-columns:1fr} }

/* A card that is itself the link. Same box as .card, plus the arrow row the work
   cards use, so a services page can point at the studies without carrying a
   fourth copy of their titles. */
a.card{text-decoration:none; color:inherit; display:flex; flex-direction:column}
.card-go{
  display:flex; align-items:center; gap:8px; margin-top:auto; padding-top:18px;
  font-size:.9rem; font-weight:600; color:var(--ink);
}
.card-go svg{width:15px; height:15px; transition:transform .18s var(--ease)}
a.card:hover .card-go svg{transform:translateX(4px)}
@media (prefers-reduced-motion:reduce){ .card-go svg{transition:none} }
.card{
  background:var(--paper); border:1px solid var(--line); border-radius:var(--r-card);
  padding:26px 24px 28px; transition:border-color .18s var(--ease), transform .18s var(--ease);
}
.cards .card:hover{border-color:var(--line-strong); transform:translateY(-2px)}
.alt .card{background:#fff}
.card h3{margin-bottom:10px}
.card p{color:var(--muted); font-size:.94rem; margin:0}
.card-ico svg{width:21px; height:21px; display:block}
.card:hover .card-ico{transform:translate(6px,-9px) rotate(22.5deg); box-shadow:0 12px 24px rgba(17,17,17,.20)}

.card-ico{
  width:42px; height:42px; border-radius:11px; background:var(--green);
  display:grid; place-items:center; margin-bottom:18px; color:var(--ink);
  transition:transform .34s cubic-bezier(.34,1.32,.64,1), box-shadow .34s var(--ease);
}

/* ── work ─────────────────────────────────────────────────────────
   platter leads its case cards with the outcome, over the image. */
.work-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(310px,1fr)); gap:20px}
/* These are anchors, not articles: the whole card is the link to the study.
   There is no global a{text-decoration:none}, so the reset has to be here or
   every card title and paragraph ships underlined. */
.work-card{
  border-radius:var(--r-card); overflow:hidden; background:#fff;
  border:1px solid var(--line); display:flex; flex-direction:column;
  text-decoration:none; color:inherit;
  transition:transform .26s var(--ease), box-shadow .26s var(--ease);
}
/* The lift already answers the mouse. Keyboard gets a real ring, since the
   card carries no other focus affordance of its own. */
.work-card:focus-visible{outline:2px solid var(--ink); outline-offset:3px}
.work-grid .work-card:hover{transform:translateY(-4px) scale(1.015); box-shadow:0 16px 40px rgba(17,17,17,.13)}
.work-shot{aspect-ratio:16/10; overflow:hidden; background:var(--paper-alt); border-bottom:1px solid var(--line)}
.work-shot img{width:100%; height:100%; object-fit:cover; object-position:top center}
.work-body{padding:22px 24px 26px}
.work-body h3{margin-bottom:8px}
.work-tag{
  display:inline-block; font-size:.66rem; font-weight:700; letter-spacing:.13em;
  text-transform:uppercase; background:var(--green); color:var(--ink);
  padding:4px 9px; border-radius:4px; margin-bottom:13px;
}
.work-body p{color:var(--muted); font-size:.93rem; margin:0}
.work-note{margin-top:14px; padding-top:14px; border-top:1px solid var(--line); font-size:.83rem; color:var(--muted)}

/* ── process ──────────────────────────────────────────────────────*/
.flow{display:grid; grid-template-columns:repeat(auto-fit,minmax(215px,1fr)); gap:24px; counter-reset:f}
.flow-step{counter-increment:f; padding-top:22px; border-top:3px solid var(--green)}
.flow-step::before{
  content:"0" counter(f); display:block; font-family:var(--display); font-weight:800;
  font-size:1.2rem; color:var(--ink); margin-bottom:12px;
}
.flow-step h3{margin-bottom:9px}
.flow-step p{color:var(--muted); font-size:.93rem; margin:0}

/* ── faq ──────────────────────────────────────────────────────────*/
.faq{max-width:820px}
.faq details{border-bottom:1px solid var(--line); padding:4px 0}
.faq summary{
  list-style:none; cursor:pointer; padding:18px 40px 18px 0; position:relative;
  font-weight:600; font-size:1rem; letter-spacing:-.01em;
}
.faq summary::-webkit-details-marker{display:none}
.faq summary::after{
  content:"+"; position:absolute; right:6px; top:50%; transform:translateY(-50%);
  font-size:1.4rem; font-weight:400; color:var(--ink); transition:transform .2s var(--ease);
}
.faq details[open] summary::after{transform:translateY(-50%) rotate(45deg)}
.faq details p{padding:0 40px 18px 0; margin:0; color:var(--muted)}

/* ── comparison table ─────────────────────────────────────────────
   Platter run this to beat freelancers; the columns here are Agency and
   DIY builder instead, because a freelancer column would be us. Our column
   carries one honest cross, which is what makes the other five believable. */
/* position:relative is load-bearing. The .sr labels in the cells are absolutely
   positioned, and the table clips with overflow:hidden but is not itself
   positioned, so without a positioned ancestor here those spans resolve against
   the initial containing block. They then escape both the clip and this scroll
   box and sit out at the table full 660px width, dragging the whole document
   208px wider than the phone and letting the page scroll sideways. */
.cmp-wrap{overflow-x:auto; -webkit-overflow-scrolling:touch; position:relative}
.cmp{
  width:100%; min-width:660px; table-layout:fixed; border-collapse:separate; border-spacing:0;
  background:#fff; border:1px solid var(--line); border-radius:var(--r-card); overflow:hidden;
}
.cmp th,.cmp td{padding:16px 12px; text-align:center; border-bottom:1px solid var(--line)}
.cmp tbody tr:last-child th,.cmp tbody tr:last-child td{border-bottom:0}
.cmp thead th,.cmp thead td{
  font-family:var(--display); font-weight:700; font-size:1.16rem; letter-spacing:-.014em;
  padding-top:22px; padding-bottom:22px; vertical-align:middle;
}
.cmp tbody th{text-align:left; font-weight:500; font-size:.94rem; color:var(--ink); padding-left:20px; line-height:1.35}
.cmp .own{background:var(--green)}
.cmp .logo-cell img{height:30px; width:auto; display:block; margin:0 auto}
.cmp .m{width:22px; height:22px; display:block; margin:0 auto}
/* clip-path rather than the deprecated clip, and a negative margin so the box
   contributes nothing to layout wherever it is used. */
.sr{position:absolute; width:1px; height:1px; margin:-1px; padding:0; border:0;
    overflow:hidden; clip-path:inset(50%); white-space:nowrap}

/* ── contact ──────────────────────────────────────────────────────
   platter's audit form is 4 fields against a wall of proof. Same idea:
   the fewest fields that still let him do the job. */
/* Formspree's honeypot: off screen rather than display:none, which bots skip. */
.gotcha{position:absolute; left:-9999px; width:1px; height:1px; opacity:0}
.form-error{margin:2px 0 0; font-size:.88rem; color:var(--danger); font-weight:600}

/* Required marker and per-field validation. The asterisk is decorative: the
   required attribute is what screen readers actually announce. */
.req{color:var(--danger); margin-left:4px; font-weight:700}
.field-err{font-size:.8rem; color:var(--danger); font-weight:600}
.field.is-invalid input,
.field.is-invalid select,
.field.is-invalid textarea{border-color:var(--danger)}
/* Focus would otherwise repaint the border black with a green ring and hide the
   error exactly while they are fixing it. */
.field.is-invalid input:focus,
.field.is-invalid select:focus,
.field.is-invalid textarea:focus{
  border-color:var(--danger); box-shadow:0 0 0 3px rgba(179,38,30,.16);
}

/* hidden must win over the display values set on .field and .form-done below,
   otherwise a branch that is switched off still renders. */
[hidden]{display:none !important}

/* ── the sent state ───────────────────────────────────────────────
   The circle lands, then the check draws, then the words arrive. Three beats
   rather than one, because the pause before a confirmation is the part that
   actually registers as "it worked". */
.form-done{text-align:center; padding:48px 24px 54px; border:1px solid var(--line); border-radius:var(--r-card)}
.done-mark{
  width:78px; height:78px; margin:0 auto 22px; border-radius:50%; background:var(--green);
  display:grid; place-items:center; animation:donePop .5s var(--ease) both;
}
.done-mark svg{width:40px; height:40px; display:block}
.done-mark path{stroke-dasharray:36; stroke-dashoffset:36; animation:doneDraw .42s .3s var(--ease) forwards}
.form-done h3{font-size:1.7rem; margin-bottom:9px; animation:doneUp .45s .46s var(--ease) both}
.form-done p{color:var(--muted); margin:0; animation:doneUp .45s .56s var(--ease) both}
@keyframes donePop{from{transform:scale(.55); opacity:0} to{transform:scale(1); opacity:1}}
@keyframes doneDraw{to{stroke-dashoffset:0}}
@keyframes doneUp{from{opacity:0; transform:translateY(9px)} to{opacity:1; transform:none}}

.panel[hidden]{display:none}

.form{display:grid; gap:16px; max-width:600px}
.field{display:grid; gap:7px}
.field label{font-size:.83rem; font-weight:600; color:var(--ink)}
.field input,.field textarea,.field select{
  width:100%; padding:13px 15px; border-radius:10px; font:inherit; font-size:.97rem;
  background:#fff; border:1px solid var(--line-strong); color:var(--ink);
  transition:border-color .15s, box-shadow .15s;
}
.field input:focus,.field textarea:focus,.field select:focus{
  outline:none; border-color:var(--ink); box-shadow:0 0 0 3px rgba(193,255,114,.55);
}
.field textarea{min-height:120px; resize:vertical}
.field-hint{font-size:.8rem; color:var(--muted)}
.form-foot{display:flex; align-items:center; gap:16px; flex-wrap:wrap; margin-top:2px}
.form-note{font-size:.83rem; color:var(--muted); max-width:32ch}

/* ── footer ───────────────────────────────────────────────────────*/
.foot-bot .lang{order:-1}
.site-foot .lang button{color:var(--muted)}
.site-foot .lang button[aria-pressed="true"]{background:var(--ink); color:#fff}
.site-foot{background:var(--paper-alt); color:var(--muted); padding:56px 0 34px; border-top:1px solid var(--line)}
.foot-top{display:flex; gap:40px; flex-wrap:wrap; justify-content:space-between; margin-bottom:38px}
.foot-brand img{height:68px; width:auto; margin-bottom:18px}
.foot-brand p{max-width:34ch; font-size:.92rem; margin:0}
.foot-links{display:flex; gap:52px; flex-wrap:wrap}
.foot-col h4{font-size:.68rem; letter-spacing:.15em; text-transform:uppercase; color:var(--ink); margin:0 0 14px; font-weight:700}
.foot-col a{display:block; text-decoration:none; font-size:.92rem; margin-bottom:10px; transition:color .15s}
.foot-col a:hover{color:var(--ink)}
.foot-bot{display:flex; gap:16px; justify-content:space-between; flex-wrap:wrap; padding-top:22px; border-top:1px solid var(--line); font-size:.83rem}

/* ── work page ────────────────────────────────────────────────────*/
.case{padding:clamp(48px,6vw,80px) 0; border-bottom:1px solid var(--line)}
.case:last-of-type{border-bottom:0}
.case-grid{display:grid; grid-template-columns:.9fr 1.1fr; gap:clamp(28px,4.5vw,60px); align-items:center}
.case:nth-of-type(even) .case-shot{order:-1}
.case-shot{border-radius:var(--r-card); overflow:hidden; border:1px solid var(--line)}
.case h2{margin-bottom:14px}
.case ul{list-style:none; padding:0; margin:20px 0 0}
.case li{padding:8px 0 8px 30px; position:relative; color:var(--muted); font-size:.94rem}

/* The card grid lives inside this section now, so it carries a full section's
   bottom padding rather than a heading's. */
.page-head{padding:clamp(60px,8vw,96px) 0 clamp(64px,8vw,104px)}
.page-head .wgrid{margin-top:clamp(40px,5vw,62px)}
/* Same merge on the pricing page: the tier grid sits in the hero section. */
.hero--plain .tiers{margin-top:clamp(40px,5vw,62px)}
.hero--plain:has(.tiers){padding-bottom:clamp(64px,8vw,104px)}
.page-head h1{margin-bottom:18px}

/* ── reveal ───────────────────────────────────────────────────────*/
.reveal{opacity:0; transform:translateY(14px)}
.reveal.in{opacity:1; transform:none; transition:opacity .55s var(--ease), transform .55s var(--ease)}

/* ── responsive ───────────────────────────────────────────────────*/
@media (max-width:960px){
  .site-head .inner{gap:24px}
  .site-nav{gap:18px}
}
@media (max-width:980px){
  /* Stacked, the hero reads promise, proof, then ask: the work itself sells
     the button harder than the button sells itself. display:contents lifts
     the copy's children into the grid so the carousel can slot between them
     without moving in the DOM, which would break the desktop two-column.
     Spacing moves to margins here because one grid gap cannot serve five
     items that each want a different distance from the next. */
  .hero-grid{grid-template-columns:1fr; gap:0}
  .hero h1{max-width:none}
  .hero-copy{display:contents}
  .hero-copy h1{order:1}
  .hero-copy .lede{order:2; margin-bottom:26px}
  .hero-visual{order:3; margin-bottom:30px}
  .hero-cta{order:4}
  /* The button's own fine print, so it sits straight underneath with no rule
     between them. The divider stays on desktop, where the two are further apart. */
  .hero-meta{order:5; margin-top:14px; padding-top:0; border-top:0}
  /* ai-search adds an eyebrow and a logo strip to the same hero. display:contents means
     anything without an explicit order defaults to 0 and jumps ahead of the h1, which put
     the logo row above the headline. Both ends of the stack need stating. */
  .hero-copy .eyebrow{order:0}
  .ai-strip__lead{order:6; margin-top:28px}
  .ai-strip{order:7}
}
@media (max-width:900px){
  .offer-grid,.case-grid{grid-template-columns:1fr}
  .case:nth-of-type(even) .case-shot{order:0}
}
@media (max-width:820px){
  :root{--head-h:92px}    /* 22 top gap + 70 bar */
  /* The bar no longer hosts the menu. It lives in .nav-panel, fixed to the
     viewport as a sibling of the header. */
  .site-nav{display:none}
  .nav-toggle{display:block}
  /* On a phone there is no hover and far less room, so the bar is simply
     always in its floating state and the mark is the wordmark alone. */
  .site-head .inner{
    gap:12px; padding:0 12px 0 18px; height:70px;
    background:rgba(255,255,255,.94); backdrop-filter:saturate(1.7) blur(12px);
    border-color:var(--line); border-radius:20px;
    box-shadow:0 10px 30px rgba(17,17,17,.10);
  }
  .site-head.floating .inner{height:70px}
  /* No transition on the bar at phone widths. Any animation inside the fixed
     header costs Safari's scroll edge effect for the rest of the page's life,
     and the bar has nothing to animate here: it is already in its floating
     look at every scroll position. Desktop keeps the motion. */
  .site-head,.site-head .inner{transition:none}
  .brand-icon{display:none}
  .brand-word{margin-left:0}
  .site-head .btn{display:none}   /* platter drops the header CTA on mobile too */

  .brand-word,
  .site-head.floating .brand-word{max-width:120px; opacity:1; margin-left:10px}
  .brand-word img,
  .site-head.floating .brand-word img{height:26px}
  .hero-cta .btn{width:100%}
  .foot-links{gap:32px}
}
@media (prefers-reduced-motion:reduce){
  .fdot.is-on::after{animation:none; width:100%}
  *{animation:none !important; transition:none !important; scroll-behavior:auto !important}
  .reveal{opacity:1; transform:none}
  /* Killing the animations would otherwise leave the confirmation on its first
     frame: an undrawn check on an invisible circle above invisible text. */
  .done-mark path{stroke-dashoffset:0}
  .done-mark,.form-done h3,.form-done p{opacity:1; transform:none}
  /* Without this the panel would sit forever on its first frame: clipped to
     the bar height with its links still 10px low and invisible. */
  .nav-panel{clip-path:none; transform:none}
  .nav-panel-links a,.nav-panel-cta{opacity:1; transform:none}
}

/* ── work cards + case studies ─────────────────────────────────────
   The work page was five stacked full-width sections in one scroll. It is now
   a grid of cards, each opening its own study page, so a fifth project (XAND)
   is one card plus one file rather than another screen-length of scroll.
   Card shape follows the platter.com case-study grid: tall photo, brand mark
   top left, headline result bottom left, both over a gradient scrim. */

/* 2-up, not platter's 3-up, and 16/10 rather than their tall 3/4. Platter's
   cards hold PHOTOGRAPHS, which crop happily. These hold screenshots, which do
   not: at 3/4 a 1440x900 capture showed only its middle third and every
   headline was sliced in half. All four captures are 16:10, so a 16/10 card at
   two across shows each one whole, at 670px, with no crop at all. */
/* 560 rather than 420: at the 1360 shell, 420 fits THREE columns and four
   studies then render as 3 + 1 orphan. 560 forces a clean 2 x 2. */
/* min(560px,100%), not a bare 560px: auto-fit will not shrink a column below
   its stated minimum, so at 390 the grid stayed 560 wide and the whole page
   scrolled sideways by 190px. */
.wgrid{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(560px,100%),1fr)); gap:22px}
/* The work card. Text used to sit ON the screenshot behind a heavy scrim, which
   meant our headline landed on top of the client's own headline and buttons: two
   sets of type competing in the same space. It reads far better split the way
   platter splits theirs, screenshot on top and copy in a white body underneath,
   so neither has to fight the other for a legible ground. The only thing left
   over the image is the trade pill, which sits in the corner over the browser
   chrome rather than over any content. */
.wcard{
  display:flex; flex-direction:column; overflow:hidden;
  border-radius:var(--r-card); background:var(--paper);
  border:1px solid var(--line); text-decoration:none; color:var(--ink);
  transition:transform .45s var(--ease), box-shadow .45s var(--ease), border-color .45s var(--ease);
}
.wcard:hover{
  transform:translateY(-4px); box-shadow:0 18px 44px rgba(17,17,17,.13);
  border-color:var(--line-strong);
}
.wcard-shot{
  position:relative; display:block; aspect-ratio:16/10; overflow:hidden;
  background:var(--paper-alt); border-bottom:1px solid var(--line);
}
.wcard-shot img{
  width:100%; height:100%; object-fit:cover;
  /* top center, so the card shows each site's hero rather than its mid-page */
  object-position:top center; display:block;
  transition:transform .6s var(--ease);
}
.wcard:hover .wcard-shot img{transform:scale(1.04)}
/* Frosted, and this is the RIDGE hero recipe rather than an approximation of it.
   Their call button is:
     background:rgba(255,255,255,.14); color:#fff;
     box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.6); backdrop-filter:blur(6px)
   which is barely tinted glass. It only READS dark because their hero is a dark
   roof photo showing through it, and that is the thing earlier attempts here got
   wrong: the pill was being made darker or lighter when what carried the effect
   was how little tint it had.

   At 14% it cannot be one pill for four cards, though. Sampling the real pixels
   under each tag gives RIDGE rgb(59,66,63) and REESE rgb(23,22,23), both dark,
   against VELD rgb(221,221,221) and NORTHGATE rgb(180,188,190), both light.
   White text at 14% white would vanish on the last two. So the glass stays at
   14% and the card says which way round it goes; a new card needs the right
   modifier or it gets no ring and no legible label. */
.wcard-tag{
  position:absolute; top:13px; left:13px; z-index:2;
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  font-size:.71rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  padding:7px 14px; border-radius:var(--r-pill);
}
/* RIDGE's own values, unchanged. */
.wcard-tag--on-dark{
  background:rgba(255,255,255,.14); color:#fff;
  box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.6);
}
/* A pale screenshot cannot take the 14% recipe. Ink text needs a light ground,
   so this washes toward white instead of tinting toward ink, and it has to do it
   harder than 14%: NORTHGATE measured rgb(179,186,189) at 1440 but rgb(124,129,130)
   at 390, because the narrower card crops to a darker slice of the same header,
   and a mid grey is the one tone where neither white nor ink text works at a
   light tint. 45% white clears it at both widths, 9.3:1 on that worst crop. */
.wcard-tag--on-light{
  background:rgba(255,255,255,.45); color:var(--ink);
  box-shadow:inset 0 0 0 1.5px rgba(255,255,255,.75);
}
/* Without blur the glass is just a 14% wash and the label loses its ground, so
   both variants fall back to something solid enough to read. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .wcard-tag--on-dark{background:rgba(17,17,17,.62)}
  .wcard-tag--on-light{background:rgba(255,255,255,.88)}
}
.wcard-body{display:flex; flex-direction:column; padding:clamp(19px,2.1vw,26px)}
.wcard-brand{
  display:block; font-family:var(--display); font-weight:800;
  font-size:.78rem; letter-spacing:.11em; color:var(--muted); margin-bottom:10px;
}
.wcard-title{
  display:block; font-family:var(--display); font-weight:800;
  font-size:clamp(1.1rem,1.45vw,1.3rem); line-height:1.22; letter-spacing:-.02em;
  color:var(--ink);
}
/* Reads as a button and is one in effect, though the whole card is the link, so
   it stays a span: a nested <a> inside an <a> is invalid and breaks the card. */
.wcard-go{
  display:inline-flex; align-items:center; gap:8px; align-self:flex-start;
  margin-top:auto; padding:9px 17px 9px 18px; border-radius:var(--r-pill);
  border:1px solid var(--line-strong); font-size:.85rem; font-weight:600;
  color:var(--ink); transition:border-color .3s var(--ease);
}
.wcard-body .wcard-go{margin-top:20px}
.wcard:hover .wcard-go{border-color:var(--ink)}
.wcard-go svg{width:14px; height:14px; transition:transform .45s var(--ease)}
.wcard:hover .wcard-go svg{transform:translateX(4px)}
.wcard:focus-visible{outline:3px solid var(--ink); outline-offset:3px}

/* ── a single case study ──────────────────────────────────────────
   One template, four pages. Adding a study means copying a file and swapping
   the strings, so the structure is deliberately plain. */

.study-head{padding:clamp(52px,7vw,88px) 0 clamp(30px,3.8vw,44px); background:var(--paper)}
.study-back{
  display:inline-flex; align-items:center; gap:8px; margin-bottom:26px;
  font-size:.9rem; font-weight:600; color:var(--muted); text-decoration:none;
}
.study-back:hover{color:var(--ink)}
.study-back svg{width:14px; height:14px}
.study-head h1{margin-bottom:18px; max-width:20ch}
.study-head .lede{font-size:1.1rem; max-width:62ch}

.study-meta{
  display:flex; flex-wrap:wrap; gap:0 46px; margin-top:34px;
  padding-top:26px; border-top:1px solid var(--line);
}
.study-meta div{padding:8px 0}
.study-meta dt{font-size:.74rem; letter-spacing:.1em; text-transform:uppercase; color:var(--muted); margin-bottom:5px}
.study-meta dd{margin:0; font-family:var(--display); font-weight:800; font-size:1.12rem; letter-spacing:-.02em}

.study-hero{border-radius:var(--r-card); overflow:hidden; border:1px solid var(--line); background:var(--paper-alt)}
/* Without this a 1200px capture sits at its natural size in a 1360px shell and
   leaves a white strip down the right. Only the RIDGE shot is 1440 wide, which
   is why it looked fine and the others did not. */
.study-hero img{width:100%; height:auto; display:block}

/* The three questions, always in this order, always the same shape. */
.study-body{display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:clamp(24px,3vw,40px)}
.study-block h2{font-size:clamp(1.35rem,2vw,1.6rem); margin-bottom:14px}
.study-block p{color:var(--ink-soft); margin:0 0 14px}
.study-block p:last-child{margin-bottom:0}
.study-block .n{
  display:inline-grid; place-items:center; width:30px; height:30px; margin-bottom:14px;
  border-radius:50%; background:var(--green); color:var(--ink);
  font-family:var(--display); font-weight:800; font-size:.86rem;
}

.study-did{max-width:70ch}
.study-did ul{list-style:none; padding:0; margin:22px 0 0}
.study-did li{padding:9px 0 9px 30px; position:relative; color:var(--ink-soft); font-size:.96rem}

/* 2fr/1fr suits a wide shot beside a phone shot. Where BOTH shots are wide,
   .stack gives them a column each instead, because a 1200x243 logo strip in a
   one-third column renders about 90px tall with a field of white under it. */
.study-shots{display:grid; grid-template-columns:2fr 1fr; gap:20px; align-items:start}
.study-shots.stack{grid-template-columns:1fr; gap:clamp(24px,3vw,38px)}
.study-shot{border-radius:var(--r-card); overflow:hidden; border:1px solid var(--line); background:var(--paper-alt)}
.study-shot img{width:100%; height:auto}
.study-cap{margin:10px 2px 0; font-size:.83rem; color:var(--muted)}

.study-note{
  margin-top:clamp(32px,4vw,48px); padding:20px 22px;
  border-radius:var(--r-card); background:var(--paper-alt); border:1px solid var(--line);
  font-size:.9rem; color:var(--muted); max-width:74ch;
}

@media (max-width:860px){
  .study-shots{grid-template-columns:1fr}
  .study-meta{gap:0 30px}
}

/* The card was an overlay that had to become a stack below 700, on an ink ground
   so the white text stayed readable. It is a stack at every width now, so that
   whole block is gone: left in place it painted the new white body black and the
   title vanished into it. All that is still worth doing on a phone is dropping
   the hover zoom, which a touch device cannot trigger deliberately anyway. */
@media (max-width:700px){
  .wcard:hover .wcard-shot img{transform:none}
  .wcard-title{font-size:1.08rem}
}

/* ── the flow strip: three phone screens in sequence ───────────────
   Used where a study needs to show a journey rather than a screen. The
   captures are real 390x844 viewport shots, so they sit at their own aspect
   with no device chrome drawn around them. */
/* Small on purpose. At three equal columns each phone rendered ~430px wide
   from a 390px capture, so every one was upscaled and read soft. Held to 232px
   they are downscaled instead, which is sharp, and the row matches the scale
   platter uses for the same kind of flow strip. */
/* Capped and spread, rather than three small cards bunched at the left of a
   1360px shell with a third of the row empty. */
.flowrow{display:flex; flex-wrap:wrap; gap:clamp(18px,2.6vw,34px);
         max-width:900px; justify-content:space-between}
.flowstep{display:flex; flex-direction:column; flex:0 1 232px; max-width:232px}
.flowshot{
  border-radius:18px; overflow:hidden; border:1px solid var(--line);
  background:var(--paper-alt); box-shadow:0 10px 30px rgba(17,17,17,.10);
}
.flowshot img{width:100%; height:auto; display:block}
.flowcap{margin-top:18px}
.flowcap .n{
  display:inline-flex; align-items:center; justify-content:center;
  width:26px; height:26px; border-radius:50%; margin-bottom:10px;
  background:var(--green); color:var(--ink);
  font-family:var(--display); font-weight:800; font-size:.78rem;
}
.flowcap h3{margin-bottom:7px}
.flowcap p{color:var(--muted); font-size:.92rem; margin:0}
/* the arrow between steps, drawn rather than an asset */
.flowstep{position:relative}
/* No connector arrow: with space-between the visual gap no longer matches the
   CSS gap, so a gap-anchored arrow lands in the wrong place. The numbered
   captions already carry the sequence. */
.flowshot{position:relative}
@media (max-width:760px){
  /* a scrolling row rather than a stack: three full width phones is a very long
     scroll for something that is one continuous flow */
  .flowrow{flex-wrap:nowrap; overflow-x:auto; gap:16px;
           scroll-snap-type:x mandatory; padding-bottom:10px;
           margin-inline:calc(-1 * var(--pad)); padding-inline:var(--pad)}
  .flowstep{flex:0 0 208px; max-width:208px; scroll-snap-align:start}
  .flowrow{max-width:none}
}

/* ── ai-search page ───────────────────────────────────────────────*/

/* Before / after. The contrast IS the argument, so the two columns are deliberately
   asymmetric: the old query is short and the new one is a whole sentence, and letting
   that length difference show is the point. */
.geo-split{
  display:grid; grid-template-columns:1fr 1.25fr; gap:clamp(18px,2.4vw,30px);
  align-items:start;
}
.geo-split__col{
  border:1px solid var(--line); border-radius:var(--r-card);
  padding:clamp(20px,2.4vw,30px); background:var(--paper);
}
.geo-split__col--now{border-color:var(--line-strong)}
/* The accent marks where the money is now, not decoration. Sits on the edge so it reads
   at a glance without tinting the panel and hurting text contrast. */
.geo-split__col--now{box-shadow:inset 3px 0 0 var(--green)}
.geo-split__tag{
  font:600 12px/1 var(--body); letter-spacing:.09em; text-transform:uppercase;
  color:var(--muted); margin:0 0 12px;
}
.geo-split__q{
  font-family:var(--display); font-weight:600;
  font-size:clamp(17px,1.5vw,21px); line-height:1.35;
  color:var(--ink); margin:0 0 14px;
}
.geo-split__col p:last-child{margin:0; color:var(--ink-soft)}

/* Numbered pillars. */
.geo-steps{display:grid; gap:clamp(16px,2vw,24px); max-width:900px}
.geo-step{
  display:grid; grid-template-columns:auto 1fr; gap:clamp(16px,2vw,24px);
  align-items:start;
  border-top:1px solid var(--line); padding-top:clamp(16px,2vw,24px);
}
.geo-step:first-child{border-top:0; padding-top:0}
.geo-step__n{
  font-family:var(--display); font-weight:700; font-size:15px; line-height:1;
  width:34px; height:34px; flex:none;
  display:grid; place-items:center; border-radius:50%;
  background:var(--green); color:var(--ink);
}
.geo-step h3{
  font-family:var(--display); font-weight:600;
  font-size:clamp(17px,1.5vw,20px); line-height:1.3; margin:0 0 8px; color:var(--ink);
}
.geo-step p{margin:0; color:var(--ink-soft)}

@media (max-width:820px){
  .geo-split{grid-template-columns:1fr}
  .geo-step{grid-template-columns:auto 1fr; gap:14px}
}

/* ── ai-search: hero visual ───────────────────────────────────────*/
/* A mock assistant answer. It is the page's whole argument in one glance: a customer asks,
   one business gets named. Deliberately not a screenshot of a real tool. */
.ai-card{
  border:1px solid var(--line); border-radius:var(--r-card);
  background:var(--paper); padding:clamp(20px,2.2vw,28px);
  box-shadow:0 1px 2px rgba(17,17,17,.04), 0 12px 34px rgba(17,17,17,.06);
}
.ai-card__bar{
  display:flex; align-items:center; gap:9px;
  padding-bottom:14px; margin-bottom:16px; border-bottom:1px solid var(--line);
}
.ai-card__dot{
  width:9px; height:9px; border-radius:50%; background:var(--green); flex:none;
}
.ai-card__app{
  font-size:12px; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted);
}
.ai-card__ask{
  font-family:var(--display); font-weight:600;
  font-size:clamp(15px,1.25vw,17px); line-height:1.45;
  color:var(--ink-soft); margin:0 0 20px;
}
.ai-card__ans p{margin:0 0 8px; color:var(--ink-soft); font-size:15px; line-height:1.55}
/* Same highlight device as the homepage h1, so the payoff word is branded the same way. */
.ai-card__pick{
  font-family:var(--display); font-weight:700;
  font-size:clamp(19px,1.8vw,24px); line-height:1.25;
  color:var(--ink); margin:0 0 10px !important;
  display:inline-block;
  background-image:linear-gradient(var(--green),var(--green));
  background-repeat:no-repeat; background-size:100% .16em; background-position:0 92%;
  padding-bottom:.06em;
}
.ai-card__why{color:var(--muted) !important; font-size:14px !important}
.ai-card__note{
  margin:18px 0 0; padding-top:14px; border-top:1px solid var(--line);
  font-size:13px; line-height:1.5; color:var(--muted);
}


@media (max-width:900px){
  .ai-card{margin-top:8px}
}

/* Real brand marks, trimmed to their alpha bounding box and all exported at the same
   height, so each one renders at the same optical size. The sources were not comparable:
   ChatGPT's was 3840x2160 with 55% empty frame, which would have rendered it tiny here.

   No chip border. Five marks in five different brand colours already carry plenty of
   visual weight, and boxing each one turns the row into clutter. The names stay as text
   beside them: on a page arguing for machine-readable content, locking the platform names
   inside image files would be its own small joke. */
.ai-strip__lead{
  font-size:12px; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted); margin:30px 0 14px;
}
.ai-strip{
  list-style:none; margin:0; padding:0;
  display:flex; flex-wrap:wrap; align-items:center; gap:10px 22px;
}
.ai-strip li{
  display:inline-flex; align-items:center; gap:8px;
  font-size:13.5px; font-weight:500; color:var(--ink-soft);
}
.ai-strip img{
  height:22px; width:auto; display:block; flex:none;
}

@media (max-width:900px){
  .ai-strip{gap:9px 16px}
  .ai-strip li{font-size:12.5px; gap:7px}
  .ai-strip img{height:19px}
}

/* A single-child .study-body collapses to one full-width column, which on a 1360px shell
   gives a 1400px line. Scoped so the four case studies, which always have two columns,
   are untouched. */
.study-body--single{max-width:74ch}

/* ── platform strip (homepage) ────────────────────────────────────*/
/* Sits under the service cards. Marks are optically balanced rather than set to one
   height: Klaviyo is a 3.4:1 wordmark and at icon height it would carry three times the
   visual mass of the Shopify bag. */
.stack-strip{
  margin-top:clamp(34px,4vw,50px); padding-top:clamp(26px,3vw,34px);
  border-top:1px solid var(--line);
}
.stack-strip__lead{
  font-size:12px; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted); margin:0 0 18px;
}
.stack-logos{
  list-style:none; margin:0; padding:0;
  display:flex; flex-wrap:wrap; align-items:center; gap:18px clamp(28px,4vw,52px);
}
.stack-logos img{display:block; width:auto}
.stack-logos li:nth-child(1) img{height:34px}
.stack-logos li:nth-child(2) img{height:28px}
.stack-logos li:nth-child(3) img{height:22px}

@media (max-width:600px){
  .stack-logos{gap:16px 26px}
  .stack-logos li:nth-child(1) img{height:28px}
  .stack-logos li:nth-child(2) img{height:23px}
  .stack-logos li:nth-child(3) img{height:16px}
}

/* ── pricing tiers ────────────────────────────────────────────────*/
/* A hero with no visual column needs its own measure, or the lede runs the full shell. */
.hero--plain .lede{max-width:64ch}

.tiers{display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:18px; align-items:stretch}
.tier{
  background:var(--paper); border:1px solid var(--line); border-radius:var(--r-card);
  padding:28px 26px 30px; display:flex; flex-direction:column; position:relative;
  transition:transform .45s var(--ease), box-shadow .45s var(--ease), border-color .45s var(--ease);
}
/* Same lift as a work card, so the two card types on the site behave alike. The
   tiers sit on white now rather than on the grey band, so the shadow is what
   separates a hovered card from the page instead of the ground doing it. */
.tier:hover{
  transform:translateY(-4px); box-shadow:0 18px 44px rgba(17,17,17,.13);
  border-color:var(--line-strong);
}
.tier--pick:hover{border-color:var(--ink)}
@media (prefers-reduced-motion:reduce){
  .tier{transition:none}
  .tier:hover{transform:none}
}
/* The chosen tier is marked with the brand accent rather than by being scaled up: an
   enlarged middle card pushes the other two down and makes the row read as unbalanced. */
.tier--pick{border-color:var(--ink)}
.tier__tag{
  position:absolute; top:-11px; left:26px;
  background:var(--green); color:var(--ink);
  font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  padding:5px 11px; border-radius:var(--r-pill); margin:0;
}
.tier h3{font-family:var(--display); font-weight:600; font-size:1.15rem; margin:0 0 10px}
.tier__price{
  font-family:var(--display); font-weight:800; font-size:clamp(1.9rem,3vw,2.4rem);
  line-height:1; margin:0 0 16px; color:var(--ink);
}
/* "vanaf" is the load-bearing word: without it the number reads as the total. */
.tier__from{
  display:block; font-family:var(--body); font-size:.8rem; font-weight:600;
  letter-spacing:.06em; text-transform:uppercase; color:var(--muted); margin-bottom:6px;
}
.tier__desc{color:var(--ink-soft); margin:0 0 18px; font-size:.95rem}
/* ── the tick ─────────────────────────────────────────────────────*/
/* One marker for every "what you get" list on the site: a brand-green circle
   with a black check in it. The tier list already drew this while the other
   three still carried a small green dash, which read as two different systems
   on one site. Black on #c1ff72 is the only safe way round: green on white is
   1.4:1 and the dash was decoration rather than a legible mark. The check is an
   inline data URI so a list needs no extra markup to get one. */
.tier__inc li::before,
.incl li::before,
.study-did li::before,
.case li::before{
  content:""; position:absolute; left:0;
  width:19px; height:19px; border-radius:50%;
  background:var(--green) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23111111' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center/11px 11px no-repeat;
}
.tier__inc li::before{top:8px}
.incl li::before{top:12px}
.study-did li::before{top:10px}
.case li::before{top:9px}

.tier__inc{list-style:none; margin:0 0 22px; padding:0}
.tier__inc li{position:relative; padding:6px 0 6px 29px; font-size:.9rem; line-height:1.45; color:var(--ink-soft)}
/* margin-top:auto pins every CTA to the bottom, so the buttons line up across cards of
   different text length instead of stepping down the row. */
.tier__cta{width:100%; justify-content:center}

.tier-else{
  margin-top:clamp(28px,3.5vw,40px); padding-top:clamp(26px,3vw,34px);
  border-top:1px solid var(--line); max-width:70ch;
}
.tier-else__q{font-family:var(--display); font-weight:700; font-size:1.15rem; margin:0 0 10px}
.tier-else p:not(.tier-else__q){color:var(--ink-soft); margin:0 0 20px}
/* Sits under the button, so it needs the margin the other way round. This is the
   dashboard / not-listed escape, kept as one quiet line rather than a second
   button: the band has one primary action and this is not it. */
.tier-else p.tier-else__alt{margin:20px 0 0; font-size:.95rem}
/* On tarieven.html this block sits under the tier grid and reads as a footnote
   to it. Standing alone in a section of its own, as it does on bouwen.html, the
   same left-aligned 70ch column left most of the band empty and looked like
   content that had run out. Centred, it reads as a deliberate hand-off. */
.tier-else--solo{
  margin-inline:auto; max-width:62ch; text-align:center;
  border-top:0; padding-top:0; margin-top:0;
}

.incl{list-style:none; margin:0; padding:0; max-width:74ch; display:grid; gap:2px}
.incl li{position:relative; padding:12px 0 12px 30px; border-top:1px solid var(--line); color:var(--ink-soft)}
.incl li:first-child{border-top:0}

@media (max-width:600px){
  .tier{padding:24px 20px 26px}
  .tier__tag{left:20px}
}
.tier__best{
  margin:auto 0 20px; padding-top:16px; border-top:1px solid var(--line);
  min-height:calc(3em + 16px);
  font-size:.86rem; color:var(--muted); line-height:1.5;
}
.tier__best b{color:var(--ink-soft); font-weight:600}
.tiers-note{
  margin:clamp(20px,2.5vw,28px) 0 0; max-width:74ch;
  font-size:.9rem; color:var(--muted); line-height:1.6;
}

.tier__cta.btn-ghost{background:var(--paper); color:var(--ink); border-color:var(--ink)}
.tier__cta.btn-ghost:hover{background:var(--ink); color:var(--paper); border-color:var(--ink)}
/* The one term worth explaining gets a link instead of a sentence. */
.tier__link{color:var(--muted); text-decoration:underline; text-underline-offset:3px; text-decoration-thickness:1px}
.tier__link:hover{color:var(--ink)}

.tier__amt{
  display:inline-block;
  background-image:linear-gradient(var(--green),var(--green));
  background-repeat:no-repeat; background-size:100% .14em; background-position:0 94%;
  padding-bottom:.04em;
}

/* ── quote page ─────────────────────────────────────────────────
   The tier chooser. Each option carries a price and it is the first decision
   on the page, so these are cards rather than the pill toggle the audit form
   used to have. Selected borrows the pricing page tile: white, ink outline. */
.form--wide{max-width:780px}
.form-row{display:grid; grid-template-columns:1fr 1fr; gap:16px}
.form-row .field{align-content:start}
.qpick{display:grid; grid-template-columns:repeat(4,1fr); gap:10px; margin-top:11px}
.qpick-opt{
  display:grid; gap:4px; align-content:start; text-align:left; position:relative;
  padding:14px 15px; border:1px solid var(--line); border-radius:14px;
  background:var(--paper); color:var(--ink); cursor:pointer; font:inherit;
  transition:background-color .18s var(--ease), border-color .18s var(--ease),
             color .18s var(--ease), box-shadow .18s var(--ease);
}
.qpick-opt:hover{border-color:var(--line-strong)}
.qpick-opt[aria-pressed="true"]{
  background:var(--paper); border-color:var(--ink); color:var(--ink);
  box-shadow:inset 0 0 0 1px var(--ink);
}
.qpick-name{font-weight:650; font-size:.9rem; line-height:1.25}
.qpick-amt{font-size:.82rem; color:var(--muted)}

/* Optimising an existing site is not a build package, so it does not stand in
   the row of tiers as a fifth column. Its own full-width row underneath says
   that on its own, and it also spares the grid a five-item layout that leaves
   an orphan at every breakpoint. */
.qpick-opt--wide{
  grid-column:1 / -1;
  grid-template-columns:1fr auto; align-items:center; gap:6px 14px;
}
@media (max-width:520px){ .qpick-opt--wide{grid-template-columns:1fr} }

/* The most-chosen pill, in the brand green tarieven.html already uses for the
   same job and hung over the top edge the same way. It marks the recommended
   tier without touching the card, which leaves the outline free to mean "this
   is the one you picked". */
.qpick-tag{
  position:absolute; top:-9px; left:14px;
  height:18px; display:inline-flex; align-items:center;
  background:var(--green); color:var(--ink);
  font-size:10px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  padding:0 8px; border-radius:var(--r-pill);
}
.qnext{list-style:none; margin:0; padding:0; counter-reset:qs; max-width:60ch}
.qnext li{
  counter-increment:qs; position:relative; padding:0 0 20px 46px;
  font-size:.97rem; line-height:1.55; color:var(--ink-soft);
}
.qnext li::before{
  content:counter(qs); position:absolute; left:0; top:-2px;
  width:30px; height:30px; border-radius:50%;
  border:1px solid var(--line-strong); display:grid; place-items:center;
  font-size:.8rem; font-weight:700; color:var(--ink);
}
@media (max-width:820px){ .qpick{grid-template-columns:repeat(2,1fr)} }
@media (max-width:560px){ .form-row{grid-template-columns:1fr} }
/* A 2x2 grid down to the narrowest phones. One column here made the
   chooser 322px tall on a 390px screen, which is most of the first view
   spent before reaching a field the visitor actually has to fill in. */
@media (max-width:360px){ .qpick{grid-template-columns:1fr} }

/* ── case study, platter shape ────────────────────────────────────*/
/* Structure lifted from the measured platter case-study page (Wild Earth): a split
   hero with the claims on the left and a green panel on the right, a timeline whose
   nodes are the mark, a before/after strip, then a green CTA band. Ours swaps
   platter's pink cubes for the LYNQ icon and their lime for --green. */

.case-hero{padding:clamp(40px,5vw,64px) 0 clamp(44px,5vw,70px); background:var(--paper)}
/* Deliberately tight top and bottom. The problem should follow the hero almost
   immediately, and the strip should follow the result without a gap to cross. */
.case-body{padding:clamp(20px,2.5vw,34px) 0 clamp(24px,3vw,38px)}
.case-hero__grid{
  display:grid; grid-template-columns:1.3fr .7fr; gap:clamp(30px,4vw,58px);
  align-items:start;
}
.case-hero h1{margin:0 0 20px; max-width:16ch}
.case-hero .lede{font-size:1.02rem; max-width:56ch; margin:0}

/* the three claims. Black disc + white tick, platter's exact treatment. */
.case-claims{list-style:none; margin:clamp(26px,3vw,34px) 0 0; padding:0; display:grid; gap:16px}
.case-claims li{display:flex; gap:13px; align-items:flex-start; font-weight:600; font-size:1rem; line-height:1.4}
.case-claims svg{
  width:22px; height:22px; flex:none; margin-top:1px;
  border-radius:50%; background:var(--green); color:var(--ink); padding:5px;
}

/* the green panel. Image inset on the green, meta grid beneath it, both inside
   the one block, so the ground reads as a card rather than as a section band. */
/* Capped as well as proportioned. Past about 470px the shot inside starts to
   compete with the headline instead of supporting it, which is roughly where
   platter holds theirs. */
.case-panel{
  background:var(--green); border-radius:22px; padding:clamp(14px,1.5vw,18px);
  max-width:470px; margin-left:auto; width:100%;
}
.case-panel__shot{
  border-radius:12px; overflow:hidden; background:var(--paper);
  aspect-ratio:16/10;
}
.case-panel__shot img{width:100%; height:100%; object-fit:cover; object-position:top center; display:block}
/* A capture that is not 16:10 takes its own ratio instead of being forced into
   the box, the same argument as .diff-shot--full. VELD's storefront is 1.92:1:
   cropping it to 16:10 ate the fourth product card and the end of the batch
   strip, and letterboxing it to 16:10 kept everything but parked the shot in a
   band of dead white, which reads as zoomed out. Its own ratio does neither, and
   the content lands at the panel's full width either way. The panel simply ends
   up shorter here than on a study whose shot really is 16:10. */
.case-panel__shot--full{aspect-ratio:auto}
.case-panel__shot--full img{height:auto; object-fit:contain}
.case-panel dl{
  display:grid; grid-template-columns:1fr 1fr; gap:clamp(14px,1.6vw,18px);
  margin:clamp(15px,1.7vw,20px) 2px 3px;
}
.case-panel dt{font-size:.86rem; color:rgba(17,17,17,.7); margin-bottom:4px}
.case-panel dd{margin:0; font-weight:700; font-size:1.08rem; color:var(--ink); line-height:1.28}

/* ── the timeline ─────────────────────────────────────────────────*/
/* One continuous rule down the left with the mark sitting on it. The rule is a
   pseudo-element on the list, not a border on each step, so it runs unbroken
   through the gaps between them. */
/* Centred as one column rather than run hard against the left edge of a 1360px
   shell, which left the right half of the page empty. platter centres theirs the
   same way and it is what makes the rule read as a spine rather than as a margin
   rule. */
.case-line{
  --rail:clamp(40px,5vw,64px);   /* gutter the line and the marks live in */
  --mark:34px;
  max-width:880px;
  position:relative; margin:0 auto; padding:0 0 0 var(--rail); list-style:none;
}
.case-line::before{
  content:""; position:absolute; left:calc(var(--rail) / 2 - 1px);
  top:calc(var(--mark) / 2); bottom:calc(var(--mark) / 2);
  width:2px; background:var(--ink);
}
/* A closing node under the result, so the rule terminates on a mark at both ends
   instead of just stopping. Empty and aria-hidden: it is punctuation, not a step,
   and a screen reader walking the list should not be told there is a fourth one. */
.case-end{position:relative; list-style:none}
.case-step{position:relative; padding-bottom:clamp(38px,5vw,58px)}
.case-step:last-child{padding-bottom:0}
/* The result step ends flush, and the terminator is pulled up out of the flow so
   its bottom edge lands on the bottom of the last line of that paragraph rather
   than floating in space under it. Zero height keeps it from adding any, and the
   rule, which ends half a mark above the list bottom, then meets its centre. */
.case-line .case-step:nth-last-child(2){padding-bottom:0}
.case-end{height:0}
.case-end .case-step__mark{top:calc(var(--mark) * -1)}
/* The mark sits on the rule and hides the length behind it, which is why it
   carries the page ground as its own background rather than being transparent.
   Steps inside an .alt section get the alt ground instead, further down. */
/* The brand icon on green, clipped to a full circle. The supplied asset is a
   square, so the radius is what makes it round: the mark sits well inside the
   inscribed circle and only green is trimmed at the corners. The circle is
   opaque, which is also what hides the length of rule running behind it, so it
   no longer needs to borrow the section ground. */
.case-step__mark{
  position:absolute; top:0;
  left:calc((var(--rail) + var(--mark)) / -2);
  width:var(--mark); height:var(--mark);
  border-radius:50%; overflow:hidden;
  background:var(--green); display:block;
}
/* The supplied square carries a lot of ground: the mark itself is only 58% of its
   width and 42% of its height. Scaled up inside the circle so it reads at 36px,
   overflow does the clipping. 1.25 is the ceiling, past that the strokes start
   running at the rim. */
.case-step__mark img{width:125%; height:125%; margin:-12.5%; display:block; max-width:none}
.case-step > h2{font-size:clamp(1.5rem,2.4vw,2rem); margin:0 0 20px}
.case-sub{margin:0 0 22px; max-width:70ch}
.case-sub:last-child{margin-bottom:0}
.case-sub h3{font-size:.98rem; font-weight:700; margin:0 0 7px; color:var(--ink)}
.case-sub p{margin:0; color:var(--ink-soft); font-size:.96rem; line-height:1.62}

/* ── the before / after strip ─────────────────────────────────────*/
/* Scrolls inside itself so the page body never scrolls sideways. No card around
   a pair: the two shots simply sit next to each other, and the pairing is read
   from the spacing, a tight gap inside a pair against a wide one between them.
   A frame around each pair only added weight and left dead space beside the
   caption. */
.diff-sec{background:var(--paper); padding-top:clamp(12px,1.6vw,22px)}
/* One word of heading and nothing else. An eyebrow and a lede here only pushed
   the shots further from the result they are evidence for. */
.diff-head{margin-bottom:clamp(26px,3vw,38px)}
.diff-head h2{margin:0}
/* The bar is hidden rather than styled. A phone overlays its scrollbar and fades
   it out, a desktop keeps a permanent classic one parked under the shots, and the
   two did not read as the same component. The affordance is carried by the grab
   cursor, the half-cut tile at the right edge and the hint underneath. */
.diff-scroll{
  overflow-x:auto; overflow-y:hidden; padding-bottom:4px;
  margin-inline:calc(var(--pad) * -1); padding-inline:var(--pad);
}
/* Hidden on a desktop only. A phone already overlays its bar and fades it out
   once you stop, which is the behaviour we want; a desktop instead parks a
   permanent classic bar with arrow buttons under the shots and never removes it.
   There is no way to ask a desktop browser for the fading kind, so it is dropped
   and the drag below carries the affordance instead. Touch is left alone. */
@media (hover:hover) and (pointer:fine){
  .diff-scroll{scrollbar-width:none; -ms-overflow-style:none; cursor:grab}
  .diff-scroll::-webkit-scrollbar{display:none}
}
.diff-scroll.is-dragging{cursor:grabbing; scroll-behavior:auto}
/* While dragging, nothing inside should start a native image drag or land a
   text selection, both of which cancel the gesture halfway through. */
.diff-scroll.is-dragging *{user-select:none}
.diff-scroll img{-webkit-user-drag:none; user-select:none}
.diff-row{
  --cell:clamp(152px,16vw,200px);   /* one phone shot */
  --tight:10px;                     /* between the two halves of a pair */
  display:flex; gap:clamp(34px,4.5vw,58px); width:max-content; align-items:flex-start;
}
/* Width is pinned to exactly two cells so the caption wraps inside the pair
   instead of stretching it, which is what left the old card mostly empty. */
.diff-pair{
  display:flex; flex-wrap:wrap; gap:var(--tight); margin:0;
  width:calc(var(--cell) * 2 + var(--tight));
}
/* one shot, so one cell wide: at pair width the empty half beside it read as a
   missing before rather than as deliberate space. */
.diff-pair--solo{width:var(--cell)}
.diff-pair--solo .diff-what{margin-top:12px}
.diff-cell{width:var(--cell)}
.diff-cell__label{
  font-size:.76rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  margin:0 0 9px; display:flex; align-items:center; gap:6px;
}
.diff-cell__label::before{content:""; width:8px; height:8px; border-radius:50%; flex:none}
.diff-cell--before .diff-cell__label{color:var(--muted)}
.diff-cell--before .diff-cell__label::before{background:var(--line-strong)}
.diff-cell--after .diff-cell__label{color:var(--ink)}
.diff-cell--after .diff-cell__label::before{background:var(--green)}
.diff-shot{
  border-radius:12px; overflow:hidden; border:1px solid var(--line);
  background:var(--paper); aspect-ratio:390/860;
}
.diff-shot img{width:100%; height:100%; object-fit:cover; object-position:top center; display:block}
/* A fixed box plus cover crops whatever is not 390x860, and it crops the sides,
   not the bottom: a shot that is shorter per unit width scales up to fill the
   height and loses its left and right edge. That ate the headline off every
   section shot on Woodsucces. Here the box takes the shot's own ratio instead,
   so the whole screen is on the card and the cards end at different heights. */
.diff-shot--full{aspect-ratio:auto}
.diff-shot--full img{height:auto; object-fit:contain}
.diff-what{flex-basis:100%; margin:12px 1px 0; font-size:.8rem; color:var(--muted); line-height:1.5}
.diff-hint{margin:14px 0 0; font-size:.82rem; color:var(--muted)}

/* ── green CTA band ───────────────────────────────────────────────*/
/* Full-bleed green ground with a black pill on it, which is the one place the
   brand green carries a whole section. Type is --ink throughout: --muted is
   3.1:1 on #c1ff72 and would fail here. */
.cta-green{background:var(--green); padding:clamp(56px,7vw,88px) 0}
.cta-green .shell{text-align:center; max-width:52ch; margin-inline:auto}
.cta-green h2{margin:0 0 14px; color:var(--ink)}
.cta-green p{margin:0 auto; color:var(--ink); font-size:1.02rem; line-height:1.6; max-width:44ch}
.cta-green__row{display:flex; gap:14px; justify-content:center; flex-wrap:wrap; margin-top:30px}
.cta-green .btn-ghost{border-color:rgba(17,17,17,.34); color:var(--ink)}
.cta-green .btn-ghost:hover{border-color:var(--ink)}

@media (max-width:900px){
  .case-hero__grid{grid-template-columns:1fr; gap:clamp(28px,4vw,38px)}
  .case-hero h1{max-width:none}
}
@media (max-width:600px){
  .case-panel dl{grid-template-columns:1fr 1fr; gap:15px}
  .case-line{--rail:34px; --mark:28px}
  .diff-row{--cell:150px}
}


/* ── WhatsApp float ───────────────────────────────────────────────────────
   Fixed bottom right on every page, offset by --pad so it lines up with the
   shell gutter rather than floating at some unrelated distance.

   z-index 55 is deliberate and sits BELOW the fixed header (60), the nav scrim
   (65) and the mobile nav panel (70). An open menu covers this button. Raising
   it above 65 puts a green circle on top of the menu overlay, which reads as a
   bug on every phone.

   The colour is official WhatsApp green with the official white mark. White on
   #25d366 is about 2:1, which would fail 1.4.11 for a UI component, but a brand
   logo is exempt and altering either half makes it stop reading as WhatsApp,
   which is the entire job of the button. This is the one borrowed colour on the
   site and it does not touch the --green rule: #c1ff72 still never carries text. */
.wa-float{
  position:fixed; z-index:55;
  right:calc(var(--pad) + env(safe-area-inset-right,0px));
  bottom:calc(var(--pad) + env(safe-area-inset-bottom,0px));
  width:56px; height:56px; border-radius:50%;
  display:grid; place-items:center;
  background:#25d366; color:#fff;
  box-shadow:0 6px 20px rgba(17,17,17,.22);
  transition:transform .18s ease, box-shadow .18s ease;
}
.wa-float:hover{transform:translateY(-2px) scale(1.04); box-shadow:0 10px 26px rgba(17,17,17,.28)}
.wa-float:focus-visible{outline:3px solid var(--ink); outline-offset:3px}
.wa-float__mark{width:30px; height:30px; display:block}

@media (max-width:600px){
  .wa-float{width:52px; height:52px}
  .wa-float__mark{width:28px; height:28px}
}
@media (prefers-reduced-motion:reduce){
  .wa-float{transition:none}
  .wa-float:hover{transform:none}
}
