/* RetailKonnekt — retailkonnekt.com
   Single stylesheet for index.html, privacy.html, terms.html */

:root{
  /* Warm beige ground, denim blue and burnt orange. */
  --bg:#F7F2E9;
  --bg-2:#F0E8DA;
  --surface:#FFFCF7;
  --surface-2:#F3ECDF;
  --line:rgba(64,48,30,.13);
  --line-2:rgba(64,48,30,.21);
  --text:#241B11;
  --muted:#57493C;
  --muted-2:#8A7A69;
  --accent:#2C5F8A;
  --accent-2:#B8470C;
  --ink:64,48,30;
  --max:1080px;
  --max-prose:760px;
  --r:16px;
}
:root{
  --ok:#2C5F8A;      --ok-t:#1E4568;
  --warn:#D97706;
  --people:#2C5F8A;  --people-t:#1E4568;
  --offer:#DB2777;   --offer-t:#BE185D;
  --gold:#B45309;
}

*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}

body{
  background:var(--bg);
  color:var(--text);
  line-height:1.6;
  font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}

.wrap{max-width:var(--max);margin:0 auto;padding:0 24px}
a{color:inherit;text-decoration:none}

:focus-visible{outline:2px solid var(--accent-2);outline-offset:3px;border-radius:4px}

.grad{
  background:linear-gradient(96deg,var(--accent),var(--accent-2));
  -webkit-background-clip:text;background-clip:text;color:transparent;
}

.skip{position:absolute;left:-9999px}
.skip:focus{left:24px;top:14px;z-index:100;background:var(--surface);
  border:1px solid var(--line-2);border-radius:8px;padding:10px 16px;font-size:14px}

/* ---------------- reveal ---------------- */

.reveal{opacity:0;transform:translateY(18px);
  transition:opacity .7s cubic-bezier(.16,1,.3,1),transform .7s cubic-bezier(.16,1,.3,1)}
.reveal.in{opacity:1;transform:none}

/* ---------------- header ---------------- */

header{
  position:sticky;top:0;z-index:50;
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  background:rgba(247,242,233,.82);
  border-bottom:1px solid var(--line);
}
nav{display:flex;align-items:center;justify-content:space-between;height:70px;gap:16px}

/* Horizontal lock-up in the bar, stacked in the footer. The strapline is only
   5% of the stacked artwork, so at nav-bar height it renders under 3px; the
   horizontal build rebuilds it at 22% and it stays legible. */
.logo{position:relative;display:inline-flex;align-items:center}
.logo img{display:block;height:48px;width:auto}
.logo-stack img{height:126px}

/* Two sweeps, both clipped by a PNG mask so the light only ever falls on the
   artwork itself. Positions are in px, not percentages: percentage offsets are
   measured against (box - image) and go strange once the layer is larger than
   its box. */
.logo-h::after,
.logo-h::before{content:'';position:absolute;inset:0;pointer-events:none}

.logo-h::after{
  background:linear-gradient(100deg,transparent 0%,rgba(255,255,255,.9) 50%,transparent 100%);
  background-repeat:no-repeat;background-size:175px 100%;
  -webkit-mask:url(assets/logo-h.png) left center/contain no-repeat;
          mask:url(assets/logo-h.png) left center/contain no-repeat;
  animation:logoShine 5.5s cubic-bezier(.4,0,.3,1) infinite;
}
@keyframes logoShine{0%,16%{background-position:-200px 0}62%,100%{background-position:480px 0}}

/* the arrow climbs, so its highlight travels up rather than across */
.logo-h::before{
  z-index:1;
  background:linear-gradient(0deg,transparent 0%,rgba(255,255,255,.95) 50%,transparent 100%);
  background-repeat:no-repeat;background-size:100% 50px;
  -webkit-mask:url(assets/logo-arrow.png) left center/contain no-repeat;
          mask:url(assets/logo-arrow.png) left center/contain no-repeat;
  animation:arrowRise 2.8s cubic-bezier(.35,0,.3,1) infinite;
}
@keyframes arrowRise{0%,20%{background-position:0 60px}70%,100%{background-position:0 -58px}}

.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  border-radius:11px;padding:13px 24px;
  font-family:inherit;font-size:14.5px;font-weight:600;
  letter-spacing:-.004em;line-height:1;white-space:nowrap;cursor:pointer;
  border:1px solid transparent;
  transition:background .18s,border-color .18s,box-shadow .22s,
             transform .16s cubic-bezier(.2,.8,.3,1),color .18s;
}
.btn:active{transform:translateY(1px)}

/* Solid warm ink, no gradient. On a paper ground a coloured gradient button is
   the thing that reads as a template; a single dark solid reads as print. */
.btn-primary{
  background:#2C5F8A;color:#FFFCF7;
  border-color:#2C5F8A;
  box-shadow:0 1px 2px rgba(44,95,138,.22),0 8px 20px -12px rgba(44,95,138,.55);
}
.btn-primary:hover{
  background:#356E9E;border-color:#356E9E;transform:translateY(-1px);
  box-shadow:0 2px 4px rgba(44,95,138,.24),0 14px 26px -14px rgba(44,95,138,.65);
}
.btn-primary:active{
  background:#234C6E;
  box-shadow:inset 0 2px 4px rgba(0,0,0,.28);
}

/* A transparent outline disappears against beige. Giving it the card surface
   and a real shadow makes it read as a button that is simply secondary. */
.btn-ghost{
  background:var(--surface);
  border-color:rgba(64,48,30,.3);
  color:var(--text);
  box-shadow:0 1px 2px rgba(64,48,30,.10),0 6px 14px -10px rgba(64,48,30,.4);
}
.btn-ghost:hover{
  background:#FFFFFF;
  border-color:var(--accent);
  color:var(--accent);
  transform:translateY(-1px);
  box-shadow:0 2px 4px rgba(44,95,138,.14),0 12px 22px -12px rgba(44,95,138,.45);
}

/* Icons inherit the text colour, so one rule covers dark buttons, outline
   buttons and footer links alike. */
.bi{
  width:17px;height:17px;flex:0 0 17px;
  fill:none;stroke:currentColor;stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round;
}
.ar{display:inline-flex;transition:transform .2s cubic-bezier(.2,.8,.3,1)}
.btn:hover .ar .bi{stroke-width:2.1}
.btn:hover .ar{transform:translateY(2px)}

/* ---------------- hero ---------------- */

/* min-height keeps the hero close to the mesh's 1440x820 aspect. Without it a
   wide viewport scales the SVG taller than the hero and `slice` crops the top
   and bottom rows of icons clean off. */
.hero{
  position:relative;overflow:hidden;isolation:isolate;text-align:center;
  min-height:780px;padding:96px 0 88px;
  display:flex;flex-direction:column;justify-content:center;
}

/* glow behind the network */
.hero::before{
  content:"";position:absolute;inset:-40% 0 auto;height:900px;pointer-events:none;z-index:-2;
  background:
    radial-gradient(46% 52% at 50% 30%,rgba(44,95,138,.30),transparent 70%),
    radial-gradient(34% 40% at 78% 20%,rgba(234,88,12,.17),transparent 70%),
    radial-gradient(30% 36% at 18% 28%,rgba(219,39,119,.16),transparent 70%);
}

/* ---------------- hero background mesh ---------------- */

.hero-viz{
  position:absolute;inset:0;z-index:-1;pointer-events:none;
  opacity:.9;
  /* Dims the middle rather than cutting a hole in it. A hole leaves a bald
     patch, and the nodes around its rim then read as deliberately arranged.
     Dimming keeps the field continuous straight under the type. */
  mask-image:radial-gradient(62% 46% at 50% 45%,
    rgba(0,0,0,.42) 6%,rgba(0,0,0,.70) 40%,#000 82%);
  -webkit-mask-image:radial-gradient(62% 46% at 50% 45%,
    rgba(0,0,0,.42) 6%,rgba(0,0,0,.70) 40%,#000 82%);
}
.hero-viz svg{display:block;width:100%;height:100%}

/* Kept shallow: this animates the whole group, so a wide swing would read as
   the entire field blinking in unison. */
.specks{transform-box:view-box;animation:twinkle 5.5s ease-in-out infinite}
@keyframes twinkle{0%,100%{opacity:.8}50%{opacity:.58}}

/* No stroke-opacity or stroke-width here on purpose: each <line> carries its
   own, scaled to how far apart its two nodes are, and CSS would override it. */
.links line{animation:dash 2.6s linear infinite}
@keyframes dash{to{stroke-dashoffset:-26}}

/* Each pulse starts on its node and translates to the far end.
   --dx/--dy are set inline per link; --d staggers them. */
.pulse{
  fill:var(--accent-2);
  transform-box:view-box;transform-origin:0 0;
  filter:drop-shadow(0 0 5px var(--accent-2));
  animation:flow 90s linear var(--d) infinite;
}
/* Travel is only the first 3.2% of the 90s cycle — about 2.9s to cross — and
   the remaining 97% is idle. With 69 pulses spread evenly around the cycle
   that leaves roughly two dots moving at any instant, so a transmission reads
   as a single event rather than as traffic. Both numbers are set together in
   tools/gen-hero.py; change one and you must change the other. */
@keyframes flow{
  0%{transform:translate(0,0);opacity:0}
  0.3%{opacity:1}
  2.7%{opacity:1}
  3.2%{transform:translate(var(--dx),var(--dy));opacity:0}
  100%{transform:translate(var(--dx),var(--dy));opacity:0}
}

/* Two independent cycles on different clocks and different per-node offsets,
   so no two icons drift or glint together. */
.node{
  transform-box:view-box;transform-origin:center;
  animation:float 6.5s ease-in-out var(--d) infinite,
            glint 9s ease-in-out var(--g) infinite;
}
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
@keyframes glint{
  0%,84%,100%{opacity:.58}
  89%{opacity:1}
  92%{opacity:.72}
  95%{opacity:1}
}

.node > circle{fill:var(--surface);stroke:rgba(44,95,138,.5);stroke-width:1.4}

.ico{fill:none;stroke:var(--accent);stroke-width:1.9;
  stroke-linecap:round;stroke-linejoin:round}

/* Tall and gradual so the field dissolves into the page instead of being cut
   off at the hero edge. Paired with #about losing its border-top, there is no
   seam between the hero and the section below it. */
.hero-fade{
  position:absolute;left:0;right:0;bottom:0;height:260px;pointer-events:none;z-index:-1;
  background:linear-gradient(180deg,
    transparent 0%,
    rgba(247,242,233,.4) 38%,
    rgba(247,242,233,.84) 68%,
    var(--bg) 100%);
}

/* The hero already dissolves into this section — a rule here would draw a
   hard line straight through the fade. */
#about{border-top:0}

/* ---------------- hero type ---------------- */

h1{
  font-size:clamp(38px,6vw,66px);line-height:1.06;
  letter-spacing:-.035em;font-weight:760;
  max-width:19ch;margin:0 auto 24px;
}

.sub{
  font-size:clamp(16px,1.9vw,19px);color:var(--muted);
  max-width:60ch;margin:0 auto 36px;line-height:1.68;
}

/* ---------------- sections ---------------- */

section{padding:96px 0;border-top:1px solid var(--line)}

.eyebrow{
  font-size:12.5px;font-weight:700;letter-spacing:.16em;
  text-transform:uppercase;color:var(--accent-2);margin-bottom:14px;
}
h2{font-size:clamp(27px,3.8vw,40px);letter-spacing:-.03em;line-height:1.15;font-weight:700;margin-bottom:18px}

.body p{color:var(--muted);font-size:17px;line-height:1.78;margin-bottom:20px;max-width:66ch}
.body p strong{color:var(--text);font-weight:600}

/* ---------------- about: copy beside the platform ---------------- */

.ab-split{display:grid;grid-template-columns:1fr minmax(300px,40%);gap:56px;align-items:center}
.ab-copy .body p{max-width:56ch}

/* unDraw art is drawn for light backgrounds — its palette is greys and whites.
   Rather than recolour 30-odd fills blind, it sits on a light panel, framed.
   Only the accent was changed, from unDraw's purple to our indigo. */
.ab-viz{
  position:relative;
  background:linear-gradient(168deg,#FFFCF7 0%,#F2EADC 100%);
  border:1px solid rgba(64,48,30,.28);
  border-radius:20px;padding:22px 20px;
  box-shadow:
    inset 0 1px 0 rgba(64,48,30,0.12),
    0 22px 48px -26px rgba(64,48,30,.13);
  animation:abFloat 8s ease-in-out infinite;
}
.ab-viz::after{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  box-shadow:inset 0 0 0 1px rgba(64,48,30,0.12);
}
.ab-viz img{display:block;width:100%;height:auto}
@keyframes abFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}

@media (max-width:900px){
  .ab-split{grid-template-columns:1fr;gap:36px}
  .ab-viz{max-width:420px;margin:0 auto}
  .ab-copy .body p{max-width:66ch}
}

/* ---------------- what we build ---------------- */

.wb-block{margin-top:68px}
.wb-head{margin-bottom:30px}
.wb-head h3{font-size:20px;font-weight:660;letter-spacing:-.018em}

.wb-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}

.wb{
  position:relative;
  background:linear-gradient(170deg,var(--surface),var(--bg-2));
  border:1px solid var(--line);border-radius:18px;
  padding:16px 16px 22px;
  transition:border-color .25s,transform .25s cubic-bezier(.2,.8,.3,1),box-shadow .25s;
}
.wb:hover{
  border-color:rgba(64,48,30,.3);transform:translateY(-3px);
  box-shadow:0 18px 40px -22px rgba(64,48,30,.13);
}

/* The animation sits in its own recessed panel — that frame is what stops it
   reading as a loose graphic dropped onto a card. */
.wb-viz{
  position:relative;overflow:hidden;
  background:
    radial-gradient(120% 80% at 50% 0%,rgba(44,95,138,.13),transparent 62%),
    rgba(64,48,30,.035);
  border:1px solid var(--line);border-radius:12px;
  margin-bottom:18px;
}
.wb-viz svg{display:block;width:100%;height:auto}

.wb h4{font-size:16.5px;font-weight:640;letter-spacing:-.012em;line-height:1.35;
  margin:0 4px 9px;transition:color .22s}
.wb:hover h4{color:var(--accent)}
.wb p{font-size:14.2px;line-height:1.62;color:var(--muted);margin:0 4px}

/* ---- shared scene vocabulary ---- */

.wb-viz text{font-family:inherit}
.wb-viz .cap{fill:var(--muted-2);font-size:10.5px;letter-spacing:.04em}
.wb-viz .ttl{fill:var(--text);font-size:13px;font-weight:600}
.wb-viz .big{fill:var(--text);font-size:21px;font-weight:700;letter-spacing:-.02em}
.wb-viz .big2{fill:var(--text);font-size:18px;font-weight:700;letter-spacing:-.02em}
.wb-viz .chip{fill:rgba(64,48,30,.12);stroke:var(--line-2);stroke-width:1}
.wb-viz .chipt{fill:var(--muted);font-size:10.5px;font-weight:600;text-anchor:middle}
.wb-viz .chip.up{fill:rgba(44,95,138,.16);stroke:rgba(44,95,138,.45)}
.wb-viz .chipt.up{fill:#1E4568}
.wb-viz .chip.on,.wb-viz .chip.in{fill:rgba(234,88,12,.15);stroke:rgba(234,88,12,.45)}
.wb-viz .chipt.on,.wb-viz .chipt.in{fill:var(--accent-2)}
.wb-viz .thumb,.wb-viz .im{fill:rgba(44,95,138,.2);stroke:rgba(44,95,138,.28);stroke-width:1}
.wb-viz .av,.wb-viz .av2{fill:rgba(44,95,138,.28);stroke:rgba(44,95,138,.45);stroke-width:1.2}
.wb-viz .ini{fill:var(--accent-2);font-size:12px;font-weight:700;text-anchor:middle}

@keyframes wbIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@keyframes wbFade{to{opacity:1}}

.wb-viz g[class^="w"]{transform-box:view-box}

/* 0 — takings tick up, sparkline draws */
.w0 .big{opacity:0;animation:wbFade .5s .2s forwards}
.w0 .chip.up,.w0 .chipt.up{opacity:0;animation:wbFade .4s 1.4s forwards}
.w0 .spark{fill:none;stroke:var(--accent-2);stroke-width:2.4;stroke-linecap:round;
  stroke-linejoin:round;stroke-dasharray:150;stroke-dashoffset:150;
  animation:wbDraw 1.5s .35s cubic-bezier(.4,0,.2,1) infinite}
@keyframes wbDraw{0%{stroke-dashoffset:150}55%,100%{stroke-dashoffset:0}}
.w0 .tip{fill:var(--accent-2);opacity:0;animation:wbFade .3s 1.5s forwards}

/* 1 — the stock figure drops by one on both sides at once */
.w1 .stk{font-size:11.5px;font-weight:700;text-anchor:middle;fill:var(--accent-2)}
.w1 .stk.a{animation:wbSwapA 4s steps(1) infinite}
.w1 .stk.b{animation:wbSwapB 4s steps(1) infinite}
@keyframes wbSwapA{0%,48%{opacity:1}49%,100%{opacity:0}}
@keyframes wbSwapB{0%,48%{opacity:0}49%,100%{opacity:1}}
.w1 .chip{animation:wbPing 4s ease-out infinite}
@keyframes wbPing{0%,44%{stroke:var(--line-2)}52%{stroke:var(--accent-2)}70%,100%{stroke:var(--line-2)}}

/* 2 — card presented, amount approved */
.w2 .cardf{fill:rgba(44,95,138,.24);stroke:rgba(44,95,138,.45);stroke-width:1.2;
  transform-box:view-box;animation:wbTap 3.6s cubic-bezier(.4,0,.3,1) infinite}
@keyframes wbTap{0%{transform:translateX(-16px);opacity:0}14%{transform:none;opacity:1}
  100%{transform:none;opacity:1}}
.w2 .mag{fill:rgba(234,88,12,.4)}
.w2 .big2{opacity:0;animation:wbFade .4s .6s forwards}
.w2 .cap{opacity:0;animation:wbFade .4s .45s forwards}
.w2 .tick{fill:none;stroke:#2C5F8A;stroke-width:3.4;stroke-linecap:round;stroke-linejoin:round;
  stroke-dasharray:34;stroke-dashoffset:34;animation:wbTick 3.6s ease-out infinite}
@keyframes wbTick{0%,28%{stroke-dashoffset:34}42%,100%{stroke-dashoffset:0}}
.w2 .ok{fill:#1E4568;font-size:11.5px;font-weight:650;opacity:0;
  animation:wbOk 3.6s ease-out infinite}
@keyframes wbOk{0%,34%{opacity:0}46%,100%{opacity:1}}

/* 3 — the shopper is recognised */
.w3 .ttl,.w3 .cap{opacity:0;animation:wbFade .45s .25s forwards}
.w3 .cap{animation-delay:.4s}
.w3 .chip.on,.w3 .chipt.on{opacity:0;animation:wbIn .5s .9s cubic-bezier(.2,.8,.3,1) forwards}
.w3 .av{transform-box:view-box;transform-origin:38px 48px;animation:wbPulse 3.4s ease-out infinite}
@keyframes wbPulse{0%,70%{transform:scale(1)}80%{transform:scale(1.07)}100%{transform:scale(1)}}

/* 4 — the slip feeds out and prints */
.w4 .slot{fill:rgba(64,48,30,.08);stroke:var(--line-2);stroke-width:1}
.w4 .paper{fill:rgba(64,48,30,0.081);stroke:var(--line-2);stroke-width:1}
.w4 .slip{transform-box:view-box;transform-origin:84px 24px;
  animation:wbFeed 4.2s cubic-bezier(.3,.9,.4,1) infinite}
@keyframes wbFeed{0%{transform:scaleY(0)}22%,88%{transform:scaleY(1)}100%{transform:scaleY(0)}}
.w4 .l{fill:rgba(64,48,30,.42);opacity:0;animation:wbLine 4.2s ease-out infinite;
  animation-delay:calc(var(--n) * .16s)}
@keyframes wbLine{0%,16%{opacity:0}26%,88%{opacity:1}100%{opacity:0}}
.w4 .tot{fill:var(--accent-2);font-size:11px;font-weight:700;opacity:0;
  animation:wbTot 4.2s ease-out infinite}
@keyframes wbTot{0%,42%{opacity:0}52%,88%{opacity:1}100%{opacity:0}}

/* 5 — typing, then the message lands and is read */
.w5 .bub{fill:rgba(64,48,30,0.054);stroke:var(--line-2);stroke-width:1}
.w5 .bub.sent{fill:rgba(234,88,12,.11);stroke:rgba(234,88,12,.35)}
.w5 .d{fill:var(--muted);animation:wbDot 1.1s ease-in-out infinite;
  animation-delay:calc(var(--n) * .16s)}
@keyframes wbDot{0%,60%,100%{opacity:.3}30%{opacity:1}}
.w5 .typing{animation:wbTyping 5.5s steps(1) infinite}
@keyframes wbTyping{0%,26%{opacity:1}27%,100%{opacity:0}}
.w5 .msg{opacity:0;animation:wbMsg 5.5s cubic-bezier(.2,.8,.3,1) infinite}
@keyframes wbMsg{0%,26%{opacity:0;transform:translateY(6px)}34%,100%{opacity:1;transform:none}}
.w5 .mt{fill:var(--text);font-size:12px;font-weight:560}
.w5 .mtc{fill:var(--muted-2);font-size:9.5px}
.w5 .tk{fill:none;stroke:var(--muted-2);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
  animation:wbRead 5.5s steps(1) infinite}
@keyframes wbRead{0%,66%{stroke:var(--muted-2)}67%,100%{stroke:var(--accent-2)}}

/* 6 — the range moves past */
.w6 .tile rect:first-child{fill:rgba(64,48,30,0.045);stroke:var(--line-2);stroke-width:1}
.w6 .pr{fill:var(--accent-2);font-size:11px;font-weight:700}
.w6 .reel{animation:wbReel 6s cubic-bezier(.6,0,.3,1) infinite}
@keyframes wbReel{0%,26%{transform:translateX(0)}42%,74%{transform:translateX(-82px)}
  90%,100%{transform:translateX(0)}}

/* 7 — the coupon stamps down */
.w7 .coup{fill:rgba(234,88,12,.09);stroke:var(--accent-2);stroke-width:1.3;
  transform-box:view-box;transform-origin:130px 48px;
  animation:wbStamp 4s cubic-bezier(.2,1.5,.4,1) infinite}
@keyframes wbStamp{0%{transform:scale(1.5) rotate(-6deg);opacity:0}
  16%{transform:none;opacity:1}100%{transform:none;opacity:1}}
.w7 .perf{stroke:rgba(234,88,12,.5);stroke-width:1.2;stroke-linecap:round}
.w7 .off{fill:var(--accent-2);font-size:19px;font-weight:800;letter-spacing:-.02em;
  text-anchor:middle;opacity:0;animation:wbFade .4s .5s forwards}
.w7 .cap2{fill:var(--muted);font-size:10.5px;text-anchor:middle;opacity:0;
  animation:wbFade .4s .7s forwards}

/* 8 — the shift starts */
.w8 .clock{fill:var(--text);font-size:15px;font-weight:700;text-anchor:middle;
  letter-spacing:-.01em;opacity:0;animation:wbFade .4s .3s forwards}
.w8 .chip.in,.w8 .chipt.in{opacity:0;animation:wbIn .45s .85s cubic-bezier(.2,.8,.3,1) forwards}
.w8 .ttl,.w8 .cap{opacity:0;animation:wbFade .4s .2s forwards}

/* 9 - the order walks through its three states */
.w9 .rail{stroke:rgba(64,48,30,.18);stroke-width:2.5;stroke-linecap:round;fill:none}
.w9 .lit{stroke:var(--ok);stroke-width:2.5;stroke-linecap:round;fill:none;
  stroke-dasharray:180;stroke-dashoffset:180;
  animation:w9line 5.4s cubic-bezier(.4,0,.3,1) infinite}
@keyframes w9line{0%,8%{stroke-dashoffset:180}62%,88%{stroke-dashoffset:0}100%{stroke-dashoffset:180}}
.w9 .stp circle{fill:var(--surface);stroke:rgba(64,48,30,.28);stroke-width:2;
  animation:w9dot 5.4s ease-out infinite;animation-delay:calc(var(--n) * .95s)}
@keyframes w9dot{0%,6%{fill:var(--surface);stroke:rgba(64,48,30,.28)}
  16%,88%{fill:var(--ok);stroke:var(--ok)}100%{fill:var(--surface);stroke:rgba(64,48,30,.28)}}
.w9 .tk9{fill:none;stroke:#FFFCF7;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;
  stroke-dasharray:14;stroke-dashoffset:14;
  animation:w9tick 5.4s ease-out infinite;animation-delay:calc(var(--n) * .95s)}
@keyframes w9tick{0%,8%{stroke-dashoffset:14}18%,88%{stroke-dashoffset:0}100%{stroke-dashoffset:14}}
.w9 .stp text{fill:var(--muted-2);font-size:10.5px;font-weight:600;text-anchor:middle;
  font-family:inherit;animation:w9lab 5.4s ease-out infinite;animation-delay:calc(var(--n) * .95s)}
@keyframes w9lab{0%,8%{fill:var(--muted-2)}18%,88%{fill:var(--text)}100%{fill:var(--muted-2)}}

/* 10 — a crate travels between two branches */
.w10 .br{fill:rgba(64,48,30,0.045);stroke:var(--line-2);stroke-width:1}
.w10 .brt{fill:var(--text);font-size:11.5px;font-weight:600;text-anchor:middle}
.w10 .rail{stroke:var(--line-2);stroke-width:1.4;stroke-dasharray:4 5;fill:none;
  animation:dash 2s linear infinite}
.w10 .crate{fill:rgba(234,88,12,.28);stroke:var(--accent-2);stroke-width:1.2;
  transform-box:view-box;animation:wbShip 4s cubic-bezier(.5,0,.5,1) infinite}
@keyframes wbShip{0%{transform:translateX(0);opacity:0}10%{opacity:1}
  70%{transform:translateX(62px);opacity:1}82%,100%{transform:translateX(62px);opacity:0}}
.w10 .qty{fill:var(--muted-2);font-size:10.5px;text-anchor:middle}

/* 11 - one product, priced for wherever the shop is */
.w11 .cur{fill:var(--text);font-size:22px;font-weight:700;letter-spacing:-.02em;
  font-family:inherit;opacity:0;
  animation:w11cur 12s steps(1) infinite;animation-delay:calc(var(--n) * 3s)}
@keyframes w11cur{0%,22%{opacity:1}25%,100%{opacity:0}}
.w11 .loc rect{fill:rgba(64,48,30,.07);stroke:rgba(64,48,30,.18);stroke-width:1;
  animation:w11box 12s steps(1) infinite;animation-delay:calc(var(--n) * 3s)}
@keyframes w11box{0%,22%{fill:rgba(44,95,138,.16);stroke:var(--accent)}
  25%,100%{fill:rgba(64,48,30,.07);stroke:rgba(64,48,30,.18)}}
.w11 .loc text{fill:var(--muted-2);font-size:10px;font-weight:700;text-anchor:middle;
  font-family:inherit;animation:w11lab 12s steps(1) infinite;animation-delay:calc(var(--n) * 3s)}
@keyframes w11lab{0%,22%{fill:var(--accent)}25%,100%{fill:var(--muted-2)}}
.w11 .cap2{fill:var(--muted-2);font-size:10px;font-family:inherit}

/* 12 — the bill totals itself, line by line */
.w12 .row{fill:var(--muted);font-size:12px}
.w12 .val{fill:var(--text);font-size:12px;font-weight:600;text-anchor:end}
.w12 .tot{font-size:14px;font-weight:700}
.w12 .val.tot{fill:var(--accent-2)}
.w12 .rule{stroke:var(--line-2);stroke-width:1;fill:none;opacity:0;
  animation:wbFade .3s .75s forwards}
.w12 .row,.w12 .val{opacity:0;animation:wbFade .4s forwards;
  animation-delay:calc(.2s + var(--n) * .38s)}

/* 13 — the connection drops, then catches up */
.w13 .wifi{fill:none;stroke:var(--muted-2);stroke-width:2.2;stroke-linecap:round}
.w13 .wifi.d{fill:var(--muted-2);stroke:none}
.w13 .slash{stroke:#D97706;stroke-width:2.4;stroke-linecap:round;fill:none}
.w13 .st{font-size:13px;font-weight:650}
.w13 .st.warn{fill:#D97706}
.w13 .st.good{fill:#1E4568}
.w13 .tick2{fill:none;stroke:#2C5F8A;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.w13 .off{animation:wbPhaseA 6s steps(1) infinite}
.w13 .syn{animation:wbPhaseB 6s steps(1) infinite}
@keyframes wbPhaseA{0%,60%{opacity:1}61%,100%{opacity:0}}
@keyframes wbPhaseB{0%,60%{opacity:0}61%,100%{opacity:1}}

/* 14 — one permission on, one off */
.w14 .tog{fill:rgba(234,88,12,.28);stroke:rgba(234,88,12,.5);stroke-width:1}
.w14 .tog.off2{fill:rgba(64,48,30,.1);stroke:var(--line-2)}
.w14 .knob{fill:var(--accent-2)}
.w14 .knob.off2{fill:var(--muted-2)}
.w14 .tog,.w14 .knob{opacity:0;animation:wbFade .4s forwards;
  animation-delay:calc(.35s + var(--n) * .3s)}
.w14 .knob{transform-box:view-box;animation:wbFade .4s forwards,wbSlide .5s cubic-bezier(.2,.9,.3,1) forwards;
  animation-delay:calc(.35s + var(--n) * .3s),calc(.35s + var(--n) * .3s)}
@keyframes wbSlide{from{transform:translateX(-20px)}to{transform:none}}
.w14 .knob.off2{animation:wbFade .4s forwards;animation-delay:calc(.35s + var(--n) * .3s)}

/* ---- colour ----------------------------------------------------------
   Everything was one shade of cyan, which reads clinical. These give each
   widget a hue that matches what it is actually about: money blue, alerts
   amber, people violet, offers pink. Declared as tokens so the palette can
   be retuned in one place.                                              */


/* money and growth read denim */
.w0 .spark{stroke:var(--ok)}
.w0 .tip{fill:var(--ok)}
.w2 .mag{fill:var(--ok)}
.w12 .val.tot{fill:var(--ok)}

/* stock warns amber as it drops */
.w1 .stk.b{fill:var(--warn)}
.w1 .chip{animation:wbPingWarn 4s ease-out infinite}
@keyframes wbPingWarn{0%,44%{stroke:var(--line-2)}52%{stroke:var(--warn)}70%,100%{stroke:var(--line-2)}}

/* people read violet */
.w3 .av,.w8 .av2{fill:rgba(44,95,138,.3);stroke:rgba(44,95,138,.55)}
.w3 .ini,.w8 .ini{fill:var(--people-t)}
.w3 .chip.on{fill:rgba(44,95,138,.16);stroke:rgba(44,95,138,.5)}
.w3 .chipt.on{fill:var(--people-t)}
.w8 .chip.in{fill:rgba(44,95,138,.16);stroke:rgba(44,95,138,.5)}
.w8 .chipt.in{fill:var(--ok-t)}

/* a sent message keeps the brand denim */
.w5 .bub.sent{fill:rgba(44,95,138,.12);stroke:rgba(44,95,138,.35)}
.w5 .tk{animation:wbReadC 5.5s steps(1) infinite}
@keyframes wbReadC{0%,66%{stroke:var(--muted-2)}67%,100%{stroke:#0284C7}}

/* the range is not all one colour */
.w6 .tile:nth-of-type(1) .im{fill:rgba(44,95,138,.22);stroke:rgba(44,95,138,.35)}
.w6 .tile:nth-of-type(2) .im{fill:rgba(180,83,9,.22);stroke:rgba(180,83,9,.35)}
.w6 .tile:nth-of-type(3) .im{fill:rgba(44,95,138,.24);stroke:rgba(44,95,138,.38)}
.w6 .pr{fill:var(--gold)}

/* an offer should look like an offer */
.w7 .coup{fill:rgba(219,39,119,.09);stroke:var(--offer)}
.w7 .perf{stroke:rgba(219,39,119,.55)}
.w7 .off{fill:var(--offer-t)}


/* stock in transit is violet */
.w10 .crate{fill:rgba(44,95,138,.3);stroke:var(--people)}


/* permissions: granted denim, withheld grey */
.w14 .tog{fill:rgba(44,95,138,.25);stroke:rgba(44,95,138,.5)}
.w14 .knob{fill:var(--ok)}

/* ---- closing panel --------------------------------------------------- */

/* One column now — the panel is a statement, not a figure with a caption.
   Generous padding and a capped measure do the work a graphic was doing. */
.wb-more{
  display:block;text-align:center;margin-top:18px;
  background:
    radial-gradient(80% 120% at 8% 50%,rgba(217,119,6,.10),transparent 60%),
    radial-gradient(80% 120% at 92% 40%,rgba(44,95,138,.14),transparent 62%),
    linear-gradient(150deg,var(--surface),var(--bg-2));
  border:1px solid var(--line);border-radius:22px;
  padding:56px 44px;
}
.wb-more-txt h4{font-size:clamp(24px,3.2vw,32px);max-width:22ch;margin-left:auto;margin-right:auto;font-weight:680;letter-spacing:-.02em;line-height:1.28;margin-bottom:12px}
.wb-more-txt p{color:var(--muted);font-size:16.5px;line-height:1.75;margin:0 auto 26px;max-width:62ch}

@media (max-width:820px){
  .wb-more{padding:36px 22px}
}

@media (max-width:940px){
  .wb-grid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:620px){
  .wb-grid{grid-template-columns:1fr;gap:14px}
  .wb-block{margin-top:48px}
}

/* ---------------- contact ---------------- */

.contact{
  position:relative;overflow:hidden;
  background:linear-gradient(150deg,var(--surface),var(--bg-2));
  border:1px solid var(--line);border-radius:22px;
  padding:60px 48px;text-align:center;
}
.contact::before{
  content:"";position:absolute;inset:-60% 0 auto;height:340px;pointer-events:none;
  background:radial-gradient(40% 60% at 50% 60%,rgba(44,95,138,.18),transparent 70%);
}
.contact > *{position:relative}
.contact p{color:var(--muted);font-size:16.5px;max-width:54ch;margin:0 auto}
.mailrow{display:flex;gap:13px;justify-content:center;flex-wrap:wrap;margin-top:32px}
.mail{
  display:inline-flex;align-items:center;gap:10px;
  border:1px solid rgba(64,48,30,.3);border-radius:11px;
  padding:13px 22px;font-size:15px;font-weight:600;
  background:var(--surface);color:var(--text);transition:.18s;
  box-shadow:0 1px 2px rgba(64,48,30,.10),0 6px 14px -10px rgba(64,48,30,.4);
}
.mail:hover{
  border-color:var(--accent);color:var(--accent);transform:translateY(-1px);
  box-shadow:0 2px 4px rgba(44,95,138,.14),0 12px 22px -12px rgba(44,95,138,.45);
}

/* ---------------- legal pages ---------------- */

.legal{padding:80px 0 20px;border-top:0}
.legal .wrap{max-width:var(--max-prose)}
.legal h1{font-size:clamp(30px,4.4vw,42px);max-width:none;margin:0 0 12px;text-align:left}
.legal .updated{color:var(--muted-2);font-size:14px;margin-bottom:44px}

.legal h2{
  font-size:20px;letter-spacing:-.01em;font-weight:660;
  margin:44px 0 14px;padding-top:26px;border-top:1px solid var(--line);
}
.legal h2:first-of-type{border-top:0;padding-top:0;margin-top:34px}
.legal h3{font-size:16px;font-weight:640;margin:26px 0 10px}
.legal p{color:var(--muted);font-size:16px;line-height:1.75;margin-bottom:16px}
.legal ul{color:var(--muted);font-size:16px;line-height:1.75;margin:0 0 16px 22px}
.legal li{margin-bottom:9px}
.legal strong{color:var(--text);font-weight:600}
.legal a:not(.logo):not(.btn){color:var(--accent-2);text-decoration:underline;text-underline-offset:3px}

.callout{
  background:var(--bg-2);border:1px solid var(--line);
  border-left:3px solid var(--accent);
  border-radius:10px;padding:18px 22px;margin:24px 0;
}
.callout p{margin:0;font-size:15px}

/* ---------------- footer ---------------- */

footer{border-top:1px solid var(--line);padding:44px 0 38px;color:var(--muted-2);font-size:13.8px}
.fbar{
  display:flex;align-items:center;justify-content:space-between;
  gap:26px;flex-wrap:wrap;padding-bottom:28px;
}
.flinks{display:flex;gap:22px;flex-wrap:wrap}
.flinks a{display:inline-flex;align-items:center;gap:7px;transition:color .15s}
.flinks a:hover{color:var(--text)}
/* One item left on this row now, so it no longer needs to be a split bar. */
.fbot{
  border-top:1px solid var(--line);padding-top:24px;
}

/* ---------------- responsive ---------------- */


@media (max-width:820px){
  section{padding:70px 0}
  .hero{min-height:660px;padding:72px 0 64px}
  .contact{padding:44px 24px}
  .legal{padding:56px 0 10px}
  .carousel-block{margin-top:46px}
}

@media (max-width:620px){
  /* Narrow screens crop the mesh horizontally, so lean on the glow instead. */
  .hero{min-height:560px}
  .hero-viz{opacity:.55}
}

@media (max-width:420px){
  nav{gap:10px;height:60px}
  .logo img{height:36px}
  .logo-stack img{height:88px}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *{transition:none!important;animation:none!important}
  /* Anything whose resting state is hidden and which relies on an animation to
     become visible has to be forced visible here, or killing the animation
     leaves it invisible for good. */
  .reveal{opacity:1;transform:none}
  .ab-viz{animation:none}
  .logo-h::before,.logo-h::after{display:none}
}
