/* ==========================================================================
   Covely.ai - design system
   Surfaces: the page is light by default and uses dark sections as
   punctuation (hero, spotlights, closing). Every colour is a per-surface
   token, so the same component works on either.
   Type:   Space Grotesk (display) + Hanken Grotesk (text), self-hosted VF
   Accent: brand pink #FF2E7E. One accent, everywhere.
   Shape:  buttons = pill, cards/media = 16px, chips/inputs = 10px
   ========================================================================== */

/* ---------- fonts (variable, self-hosted) ---------- */
@font-face{font-family:'Hanken Grotesk';font-style:normal;font-weight:300 800;font-display:swap;
  src:url('fonts/271d5973966f509f.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Hanken Grotesk';font-style:normal;font-weight:300 800;font-display:swap;
  src:url('fonts/6748e3a477b39cc0.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:300 700;font-display:swap;
  src:url('fonts/cbee7234c1d753e7.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Space Grotesk';font-style:normal;font-weight:300 700;font-display:swap;
  src:url('fonts/1efa87763419826a.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}

/* ---------- global, surface-independent ---------- */
:root{
  --accent:#FF2E7E;         /* fills, borders, bars                        */
  --accent-hi:#FF63A0;
  --accent-ink:#0A0C14;     /* text on an accent fill, 5.9:1               */
  --r-card:16px; --r-chip:10px; --r-pill:999px;
  --maxw:1216px;
  --gut:clamp(20px,5vw,40px);
  --sec:clamp(76px,9.5vw,142px);
  --ease:cubic-bezier(.16,1,.3,1);          /* strong ease-out, entrances   */
  --ease-out:cubic-bezier(.23,1,.32,1);     /* stronger, for reveals        */
  --ease-in-out:cubic-bezier(.77,0,.175,1); /* on-screen movement           */
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
}

/* ---------- surfaces ---------- */
.s-light{
  --bg:#FFFFFF; --bg-2:#F3F5F9;
  --panel:#FFFFFF; --panel-2:#F7F8FB;
  --fg:#0B1020; --fg-2:#4E566E; --fg-3:#5E6680;
  --line:rgba(11,16,32,.10); --line-2:rgba(11,16,32,.20);
  --accent-fg:#C71E5C;                    /* accent as text, 5.5:1 on white */
  --accent-dim:rgba(255,46,126,.10);
  --hair:rgba(11,16,32,.06);
  --shadow:0 18px 42px -26px rgba(11,16,32,.30);
  --shadow-lg:0 30px 70px -34px rgba(11,16,32,.38);
  --code-bg:#0C1020; --code-fg:#E4E7F2;
  color:var(--fg);background:var(--bg);
}
.s-dark{
  --bg:#0A0C14; --bg-2:#0D1020;
  --panel:#12162A; --panel-2:#171C33;
  --fg:#F3F4F8; --fg-2:#A9B0C6; --fg-3:#8A92AC;
  --line:rgba(255,255,255,.10); --line-2:rgba(255,255,255,.20);
  --accent-fg:#FF3E88;                    /* accent as text, 5.2:1 on ink   */
  --accent-dim:rgba(255,46,126,.16);
  --hair:rgba(255,255,255,.07);
  --shadow:0 22px 54px -30px rgba(0,0,0,.85);
  --shadow-lg:0 34px 80px -34px rgba(0,0,0,.95);
  --code-bg:#080B16; --code-fg:#D8DCEC;
  color:var(--fg);background:var(--bg);
}
.bg-2{background:var(--bg-2)}

/* ---------- reset ---------- */
*,*::before,*::after{box-sizing:border-box}
*{margin:0;padding:0}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;background:#fff}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{
  font-family:'Hanken Grotesk',system-ui,-apple-system,BlinkMacSystemFont,sans-serif;
  font-size:17px;line-height:1.6;font-weight:400;overflow-wrap:break-word;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
img,svg,video{display:block;max-width:100%}
.ico{width:18px;height:18px;flex:0 0 auto}
a{color:inherit;text-decoration:none}
button,input,textarea,select{font:inherit;color:inherit}
ul,ol{list-style:none}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:4px}
::selection{background:var(--accent);color:#fff}

/* ---------- type ---------- */
.d0,.d1,.d2,.d3,.d4,.num{font-family:'Space Grotesk','Hanken Grotesk',sans-serif;font-weight:600;letter-spacing:-.03em;color:var(--fg)}
.d0{font-size:clamp(2.35rem,5.1vw,3.85rem);line-height:1.04}
.d1{font-size:clamp(2.05rem,4.2vw,3.15rem);line-height:1.07}
.d2{font-size:clamp(1.55rem,2.5vw,2.15rem);line-height:1.14;letter-spacing:-.025em}
.d3{font-size:clamp(1.2rem,1.7vw,1.4rem);line-height:1.22;letter-spacing:-.02em}
.d4{font-size:1.0625rem;line-height:1.3;letter-spacing:-.015em;font-weight:600;color:var(--fg)}
.lede{font-size:clamp(1.0625rem,1.45vw,1.2rem);line-height:1.55;color:var(--fg-2);max-width:62ch}
.small{font-size:.9375rem;line-height:1.52;color:var(--fg-2)}
.tiny{font-size:.8125rem;line-height:1.45;color:var(--fg-3)}
strong,b{font-weight:600;color:var(--fg)}
.eyebrow{
  display:inline-flex;align-items:center;gap:9px;
  font-family:'Space Grotesk',sans-serif;font-size:.75rem;font-weight:600;
  text-transform:uppercase;letter-spacing:.14em;color:var(--accent-fg);
}
.eyebrow::before{content:"";width:22px;height:1px;background:currentColor;opacity:.65}

/* ---------- layout ---------- */
.wrap{width:100%;max-width:var(--maxw);margin:0 auto;padding-inline:var(--gut)}
.wrap-wide{max-width:1440px}
.wrap-narrow{max-width:900px}
.sec{padding-block:var(--sec);position:relative}
.sec-tight{padding-block:clamp(54px,6vw,86px)}
/* two neighbours on the same surface still read as two sections */
.s-light + .s-light{border-top:1px solid var(--hair)}
/* Dark meeting dark gets no such hairline -- a line on the dark ground reads as
   a seam rather than a boundary -- so the pair was both paddings with nothing
   between them: 274px of unbroken near-black at 1440, with the ads band's one
   caption floating in the middle of it. Reported as exactly that. The pair
   shares one section's worth of space now instead of two. Two places do it: the
   homepage, ads into AI Avatar, and the use-cases hub. :has() is what lets the
   first band know; without it only the second half collapses, which is still
   half the void. */
.sec.s-dark + .sec.s-dark{padding-top:clamp(34px,4vw,64px)}
.sec.s-dark:has(+ .sec.s-dark){padding-bottom:clamp(34px,4vw,64px)}
.hd{max-width:min(760px,100%);display:grid;gap:16px}
.hd-wide{max-width:860px}
/* A section header on one line: the title on the left, one link on the right.
   For a band whose title needs no explaining -- it saves the two-line lede and
   the row under it, and stops the right half of the band being empty. */
.hd-line{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:12px 28px}
.hd-line>.d1{margin:0}
@media (max-width:559px){.hd-line{gap:10px}}
/* Two lines and then an ellipsis. Cards in a row have to agree on height, and
   a teaser is the one field whose length nobody controls. */
.clamp-2{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
.stack{display:grid;gap:20px}
.row{display:flex;flex-wrap:wrap;gap:12px;align-items:center}

/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:13px 22px;border-radius:var(--r-pill);border:1px solid transparent;
  font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:600;letter-spacing:-.01em;
  white-space:nowrap;cursor:pointer;background:none;color:var(--fg);
  transition:transform .18s var(--ease),background-color .18s ease,border-color .18s ease,color .18s ease;
}
.btn .ico{width:17px;height:17px}
.btn-pri{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
.btn-sec{border-color:var(--line-2);color:var(--fg)}
.btn-ghost{padding-inline:6px}
/* magnetic offset lives in a variable so hover and press can compose with it */
.btn{transform:translate3d(var(--mgx,0px),var(--mgy,0px),0)}
@media (hover:hover) and (pointer:fine){
  .btn-pri:hover{background:var(--accent-hi);border-color:var(--accent-hi);transform:translate3d(var(--mgx,0px),calc(var(--mgy,0px) - 1px),0)}
  .btn-sec:hover{border-color:var(--fg-3);background:var(--panel-2);transform:translate3d(var(--mgx,0px),calc(var(--mgy,0px) - 1px),0)}
  .s-dark .btn-sec:hover{background:rgba(255,255,255,.06)}
  .btn-ghost:hover{color:var(--accent-fg)}
}
.btn:active{transform:translate3d(var(--mgx,0px),var(--mgy,0px),0) scale(.97)}
.btn-lg{padding:15px 26px;font-size:1rem}
.btn-sm{padding:10px 17px;font-size:.875rem}
.link{
  width:fit-content;display:inline-flex;align-items:center;gap:7px;color:var(--fg);font-weight:600;
  font-family:'Space Grotesk',sans-serif;font-size:.9375rem;
  border-bottom:1px solid var(--line-2);padding-bottom:3px;
  transition:color .18s ease,border-color .18s ease;
}
.link .ico{width:15px;height:15px;transition:transform .2s var(--ease)}
.link:hover{color:var(--accent-fg);border-color:var(--accent)}
.link:hover .ico{transform:translate(2px,-2px)}
button.link{background:none;border:0;border-bottom:1px solid var(--line-2);padding:0 0 3px;cursor:pointer;font-size:inherit}

/* ---------- skip link ---------- */
.skip{
  position:fixed;top:0;left:50%;transform:translate(-50%,-120%);z-index:100;
  padding:12px 20px;border-radius:0 0 12px 12px;
  background:var(--accent);color:#fff;
  font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:600;
  transition:transform .18s var(--ease);
}
.skip:focus-visible{transform:translate(-50%,0);outline-offset:-4px}
main:focus{outline:none}

/* ---------- header (light chrome) ---------- */
.top-sentinel{position:absolute;top:0;left:0;width:1px;height:1px}
/* White chrome over a dark hero: the division at the top of the page is the
   design, and the same bar still reads over every light section below. */
/* the sticky header's own height, published so a full-screen hero can subtract
   it: the header is in flow, so 100svh of hero underneath it overflows by this
   much. Two values, one per nav breakpoint.

   The breakpoint is 1119, not 1059, and the sixty pixels are the language
   picker's. Measured in Chromium: at 1060 the German bar had 31px between
   "Preise" and the buttons on the right, and the picker is 79 of them wide, so
   the bar overflowed by 35px in German and in no other language. 1100 stopped
   the overflow but left five pixels of tolerance, which is not tolerance; 1120
   leaves 25, and 1140 leaves 43.

   German is the whole constraint -- Italian has 123px to spare at the same
   width. Nothing real sits in 1060-1119 (iPad 1024, iPad Air 1180, iPad Pro
   1194), and below it the picker is in the drawer anyway. */
:root{--nav-h:72px}
@media (max-width:1119px){:root{--nav-h:62px}}
.hdr{
  position:sticky;top:0;z-index:60;
  background:#fff;border-bottom:1px solid rgba(11,16,32,.08);
  transition:box-shadow .28s ease,background-color .28s ease;
}
.hdr.is-stuck{
  background:rgba(255,255,255,.86);
  backdrop-filter:saturate(150%) blur(16px);-webkit-backdrop-filter:saturate(150%) blur(16px);
  box-shadow:0 12px 32px -22px rgba(11,16,32,.4);
}
.nav{position:relative;max-width:1440px;margin:0 auto;padding-inline:var(--gut);height:var(--nav-h);display:flex;align-items:center;gap:clamp(10px,1.8vw,26px)}
.brand{display:flex;align-items:center;gap:10px;flex:0 0 auto}
.brand img{height:31px;width:auto}
.nav-main{display:flex;align-items:center;gap:2px;flex:1 1 auto}
.nav-end{display:flex;align-items:center;gap:8px;flex:0 0 auto}
.nav-btn{
  position:relative;display:inline-flex;align-items:center;gap:6px;padding:10px 13px 12px;border-radius:var(--r-chip);
  border:0;cursor:pointer;color:#4E566E;background:none;
  font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:500;
  transition:color .16s ease;
}
.nav-btn .ico{width:13px;height:13px;opacity:.6;transition:transform .24s var(--ease)}
/* the underline carries the state, so the label never moves */
.nav-btn::after{
  content:"";position:absolute;left:13px;right:13px;bottom:3px;height:2px;border-radius:2px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;transition:transform .3s var(--ease);
}
.nav-btn:hover,.nav-item:hover>.nav-btn,.nav-item:focus-within>.nav-btn,.nav-btn[aria-current]{color:#0B1020}
.nav-btn:hover::after,.nav-item:hover>.nav-btn::after,.nav-item:focus-within>.nav-btn::after,.nav-btn[aria-current]::after{transform:scaleX(1)}
.nav-item{position:relative}
/* the chevron turns over when the panel opens; the flag beside it must not --
   it is a picture, not a state indicator */
.nav-item:hover>.nav-btn .ico:not(.flag),.nav-item:focus-within>.nav-btn .ico:not(.flag){transform:rotate(180deg)}

/* ---------- mega menu ----------
   One panel width for every menu, anchored to the nav rather than to the item,
   so the menus read as one surface that changes content. The right-hand column
   previews whichever item the pointer is on, using :has() and no script. */
/* ---------- the mega menu ----------
   Links only. It used to give half the panel to a preview image that swapped on
   hover: eighteen pictures across four menus, none of them showing the page you
   were about to open, and one of them carrying a third-party mark on a shoe. A
   navigation menu's whole job is to be read quickly, and a picture that changes
   under the pointer competes with the reading.

   What replaces the image is room: the panel is narrower, the rows are taller,
   and the colour does the work the photograph was doing. */
.mega{
  position:absolute;top:calc(100% + 9px);left:0;right:auto;z-index:61;
  width:min(380px,calc(100vw - 32px));
  border-radius:18px;overflow:hidden;
  border:1px solid rgba(11,16,32,.08);
  /* Two grounds, not one flat white: a barely-there wash from the accent in the
     top-left corner, over a vertical lift from white to the palest grey. It is
     what makes the panel read as a surface rather than a box. */
  background:
    radial-gradient(120% 90% at 0% 0%,rgba(255,46,126,.075),rgba(255,46,126,0) 58%),
    linear-gradient(180deg,#FFFFFF 0%,#FBFBFD 62%,#F7F8FB 100%);
  box-shadow:
    0 34px 70px -32px rgba(11,16,32,.34),
    0 8px 20px -12px rgba(11,16,32,.10),
    0 1px 0 rgba(255,255,255,.9) inset;
  opacity:0;visibility:hidden;transform:translateY(-8px) scale(.994);transform-origin:top center;
  transition:opacity .22s ease,transform .3s var(--ease),visibility .22s;
}
/* an invisible bridge across the gap, so the panel survives the trip down */
.mega::after{content:"";position:absolute;top:-11px;left:0;right:0;height:11px}
/* Hover, or the class. Focus is deliberately not in here: a mouse click
   focuses the button, and the :focus-within that used to be in this rule then
   held the panel open while the pointer moved on to the next menu -- two
   panels over the page at once, until you clicked somewhere else. Worse, it
   fought the script: Escape removed the class, the focus it returned to the
   button re-opened the panel through CSS.

   So keyboard focus opens a menu in covely.js, by adding .is-open, which
   Escape can take away again. The .no-js rule below keeps the keyboard path
   working when the script never runs. */
.nav-item:hover .mega,.mega.is-open{opacity:1;visibility:visible;transform:none}
.no-js .nav-item:focus-within .mega{opacity:1;visibility:visible;transform:none}
/* accent hairline that wipes across the top as the panel arrives */
.mega::before{
  content:"";position:absolute;z-index:2;top:0;left:0;right:0;height:2px;
  background:linear-gradient(90deg,var(--accent),rgba(255,46,126,.25) 55%,rgba(255,46,126,0));
  transform:scaleX(0);transform-origin:left;transition:transform .55s var(--ease) .05s;
}
.nav-item:hover .mega::before,.mega.is-open::before{transform:scaleX(1)}
.no-js .nav-item:focus-within .mega::before{transform:scaleX(1)}

/* Two columns for the menus that have five and six entries, one for the rest.
   The panel widens with the column, not with a picture. */
.mega-c2{width:min(660px,calc(100vw - 32px))}
/* The panel hangs off its own item, so its right edge is that item's x plus
   this width, and CSS cannot clamp it to the viewport on its own. Resources is
   the binding case: at the narrowest width the bar is shown at all it starts
   near x=485, so the panel has to come down with it. */
@media (max-width:1319px){
  .mega{width:min(340px,calc(100vw - 32px))}
  .mega-c2{width:min(560px,calc(100vw - 32px))}
}

.mega-list{display:grid;gap:2px;padding:14px}
.mega-c2 .mega-list{grid-template-columns:repeat(2,minmax(0,1fr));gap:2px 8px}
.mega-link{
  display:flex;gap:12px;align-items:flex-start;padding:11px 12px;border-radius:12px;
  position:relative;
}
/* The hover state is the elegance: a tinted card with a hairline, rather than a
   flat grey fill. The accent shows up in the tint, the rule and the icon at
   once, so one gesture lights the whole row. */
.mega-link::before{
  content:"";position:absolute;inset:0;border-radius:inherit;z-index:-1;
  background:linear-gradient(180deg,rgba(255,46,126,.075),rgba(255,46,126,.03));
  box-shadow:inset 0 0 0 1px rgba(255,46,126,.14);
  opacity:0;transition:opacity .18s ease;
}
.mega-link:hover::before,.mega-link:focus-visible::before{opacity:1}
.mega-ico{
  flex:0 0 auto;width:32px;height:32px;display:grid;place-items:center;border-radius:10px;
  background:rgba(255,46,126,.09);color:#C71E5C;
  transition:background-color .2s ease,color .2s ease,transform .2s var(--ease);
}
.mega-link:hover .mega-ico{background:var(--accent);color:#fff;transform:scale(1.06)}
.mega-ico .ico{width:16px;height:16px}
.mega-t{
  font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:600;
  letter-spacing:-.012em;display:block;color:#0B1020;
}
.mega-d{font-size:.78125rem;line-height:1.45;color:#5E6680;display:block;margin-top:2px}
@media (prefers-reduced-motion:no-preference){
  .mega-link{opacity:0;transform:translateY(8px);transition:opacity .34s var(--ease),transform .34s var(--ease)}
  .nav-item:hover .mega-link,.nav-item:focus-within .mega-link,.mega.is-open .mega-link{
    opacity:1;transform:none;transition-delay:calc(var(--n,0)*34ms + 50ms);
  }
}
/* La bandiera non è mai sola: sta sempre accanto al nome della lingua o alla
   sua sigla, perché una bandiera è un paese e una lingua non lo è. Il font
   delle emoji la disegna a un'altezza diversa dal testo accanto, quindi
   line-height:1 e un filo di spinta verso il basso la allineano alla riga. */
/* The flags were emoji until now, and on Windows that meant no flag at all:
   Segoe UI Emoji ships no country flags on purpose, so Chrome and Edge there
   drew the two regional-indicator letters -- a boxed I and T where Italy should
   be. Drawn as SVG they are the same on every machine, and round, which is what
   a 16px flag wants to be: a rectangle that small is a smear of three colours,
   a disc reads as an object.

   They ride in the icon sprite like every other icon, so they cost one symbol
   each and no extra request. The hairline is currentColor at low opacity, which
   is how one file works on the white header and on the near-black footer: it
   keeps the white stripe of France and the white of the Union Jack from
   dissolving into whatever is behind them, without ever being a visible ring. */
.ico.flag{
  width:17px;height:17px;flex:0 0 auto;
  margin-right:.5em;
  /* The reset at the top makes every svg display:block, which is right for a
     picture and wrong for a flag sitting inside a sentence: in the drawer's
     summary it took a line of its own above the word it belongs to. Everywhere
     else the flag is a flex item, where neither of these two lines has any
     effect -- so this is the inline case only. An svg's baseline is its bottom
     edge, hence the nudge. */
  display:inline-block;vertical-align:-.28em;
  opacity:1;
}

/* ---------- language picker (top right) ---------- */
/* A [data-menu] like the four mega menus, which is the point: covely.js already
   drives that shape -- click, aria-expanded, keyboard open, Escape, one panel
   open at a time -- so this control costs no script at all.

   Two letters and not "Deutsch": the full name is 125px wide and the German bar
   has 70px to spare at 1100. See the --nav-h note above for the measurements. */
.nav-lang>.nav-btn{padding-inline:10px;gap:5px}
.nav-lang .flag{width:18px;height:18px;margin-right:3px}
.nav-lang>.nav-btn::after{left:10px;right:10px}
.nav-lang-code{
  font-family:'Space Grotesk',sans-serif;font-size:.875rem;font-weight:600;
  letter-spacing:.02em;font-variant-numeric:tabular-nums;
}
/* .mega is hard-anchored left:0, which on the last item of the bar would hang
   the panel off the right edge of the window. */
.mega-lang{
  left:auto;right:0;
  width:auto;min-width:188px;
}
/* The same panel, opening upward: the footer picker sits at the bottom of the
   document, where a panel dropping down would open into nothing and push the
   page taller. Three things flip with it -- the anchor, the direction it grows
   from, and the invisible bridge that keeps the panel alive while the pointer
   crosses the gap, which has to be on the side the pointer comes from. */
.mega-up{
  top:auto;bottom:calc(100% + 9px);
  transform:translateY(8px) scale(.994);transform-origin:bottom center;
}
.mega-up::after{top:auto;bottom:-11px}
.mega-up::before{top:auto;bottom:0}
.lang-list{display:grid;padding:8px}
.lang-list a{
  display:flex;align-items:center;gap:2px;
  padding:9px 11px;border-radius:11px;
  font-size:.9375rem;color:#4E566E;
  transition:background-color .16s ease,color .16s ease;
}
.lang-list a:hover{background:rgba(11,16,32,.045);color:#0B1020}
/* The language you are already reading is shown and not linked away from: a
   live link to where you already are is a small lie. Same rule as .ft-lang. */
.lang-list a[aria-current]{color:#0B1020;font-weight:600;pointer-events:none}
.lang-list a[aria-current]::after{
  content:"";flex:0 0 auto;width:7px;height:7px;border-radius:50%;background:var(--accent);
  margin-left:auto;
}

/* Between 1100 and 1280 the German bar is full to the millimetre: measured,
   "Preise" ends exactly one flex gap before the language button and there is
   nothing left over. It does not overflow, but a font that falls back to a
   wider face, or one longer word in a menu, and the labels would run into the
   buttons on the right. Four pixels off each of the five menu items gives that
   band forty pixels of real room, and at these widths nobody can see the
   difference. The picker sets its own padding and is not in this rule. */
@media (max-width:1279px){
  .nav-main .nav-btn{padding-inline:9px}
  .nav-main .nav-btn::after{left:9px;right:9px}
}

/* No breakpoint below this: under 1120 the whole bar is display:none and the
   drawer takes over. */

/* ---------- mobile nav ---------- */
.nav-toggle{display:none;width:42px;height:42px;border:1px solid rgba(11,16,32,.16);border-radius:var(--r-chip);background:none;color:#0B1020;cursor:pointer;place-items:center}
.nav-toggle .ico{width:19px;height:19px}
.drawer{display:none}
@media (max-width:1119px){
  .nav-main{display:none}
  .nav-end .btn-ghost{display:none}
  .nav-end .nav-lang{display:none}
  .nav-toggle{display:grid}
  .nav{height:var(--nav-h)}
  .drawer{
    display:block;position:fixed;inset:62px 0 0;z-index:59;background:#fff;color:#0B1020;
    overflow-y:auto;padding:18px var(--gut) 56px;
    opacity:0;visibility:hidden;transform:translateY(-8px);
    transition:opacity .22s ease,transform .26s var(--ease),visibility .22s;
  }
  .drawer.is-open{opacity:1;visibility:visible;transform:none}
  body.nav-locked{overflow:hidden}
  .dgroup{border-bottom:1px solid rgba(11,16,32,.09);padding:4px 0}
  .dgroup>summary{
    display:flex;justify-content:space-between;align-items:center;gap:10px;padding:15px 2px;cursor:pointer;
    font-family:'Space Grotesk',sans-serif;font-size:1.0625rem;font-weight:600;list-style:none;color:#0B1020;
  }
  .dgroup>summary::-webkit-details-marker{display:none}
  .dgroup>summary .ico{width:16px;height:16px;color:#5E6680;transition:transform .2s var(--ease)}
  .dgroup[open]>summary .ico:not(.flag){transform:rotate(180deg)}
  .dlinks{display:grid;gap:1px;padding:2px 0 12px}
  .dlinks a{display:flex;gap:11px;align-items:center;padding:11px 2px;color:#4E566E;font-size:.9375rem}
  .dlinks a:hover{color:#0B1020}
  .dlinks .mega-ico{width:28px;height:28px}
  .dlinks .mega-ico .ico{width:15px;height:15px}
  /* The language group. The current language is named in the summary, because
     on a phone the bar has no room to show it and a reader should not have to
     open a menu to find out what language they are in. */
  .dlang>summary>span{display:flex;align-items:baseline;gap:9px;min-width:0}
  .dlang-now{font-size:.9375rem;font-weight:500;color:#5E6680}
  .dlang-now .flag{margin-right:.35em}
  .dlang-links a{justify-content:flex-start}
  .dlang-links a[aria-current]{color:#0B1020;font-weight:600;pointer-events:none}
  .dlang-links a[aria-current]::after{
    content:"";flex:0 0 auto;width:7px;height:7px;border-radius:50%;background:var(--accent);
    margin-left:auto;
  }
  .dflat{display:flex;justify-content:space-between;align-items:center;padding:15px 2px;font-family:'Space Grotesk',sans-serif;font-size:1.0625rem;font-weight:600;border-bottom:1px solid rgba(11,16,32,.09);color:#0B1020}
  .dflat .ico{width:16px;height:16px;color:#5E6680}
  .drawer-cta{display:grid;gap:10px;margin-top:24px}
  .drawer-cta .btn{width:100%}
}

/* ---------- hero ---------- */
.hero{position:relative;padding-top:clamp(40px,5.4vw,78px);padding-bottom:clamp(46px,5.6vw,84px);overflow:hidden}
.hero::before{
  content:"";position:absolute;top:-26%;right:-6%;width:min(920px,88vw);aspect-ratio:1;z-index:0;
  background:radial-gradient(circle,rgba(255,46,126,.20),rgba(255,46,126,.05) 44%,transparent 68%);
  pointer-events:none;
}
.hero>*{position:relative;z-index:1}
.hero+section.sec{padding-top:clamp(34px,4.4vw,64px)}

/* ---- the hero's collage: five generations, offset, one over the other ----

   The container is the square the hero gives it and every card is placed inside
   it in per-cent, so the whole collage scales with the column and nothing has to
   be re-measured per breakpoint. Positions come from the markup because they are
   content decisions, not layout ones -- see content/videos.mjs.

   Two elements per card. The span carries the placement and the rotation; the
   figure inside carries the entrance. They both want `transform`, and one
   element cannot animate to a rotation it is also being given statically
   without the keyframe having to know each card's angle.

   Everything is visible in the base state, which is what a visitor under
   prefers-reduced-motion sees: the finished collage, held. The animation only
   adds the arrival. */
/* ---- the homepage hero wall ----------------------------------------------
   Three columns of creative scrolling behind the headline, and everything stops
   when you point at one card.

   The loop: each column carries its set of cards twice and the track travels
   -50%, which is exactly one set whatever the individual heights are -- so a
   column can mix a square ad and a 9:16 clip and still close cleanly. The
   spacing is a margin on every card and not a grid gap: with a gap the seam
   between the two sets is one gap short and the wall ticks once per cycle.

   The lighting: the dimmer is an overlay's opacity on ::after, not a filter --
   filter on fifteen cards would hand the compositor all of them at once while
   two clips are decoding. The glow is a box-shadow on the card itself rather
   than on ::after, because .hcard clips its own overflow and would cut a
   shadow drawn by a child. Nothing animates unless somebody is pointing.

   The tilt and the scale are what make it a wall rather than three lists: the
   inner grid is turned 7 degrees and drawn 22% larger than the box that clips
   it, so the columns run off the top, bottom and right edges. */
.hwall{
  position:relative;overflow:hidden;
  height:min(calc(100svh - var(--nav-h) - 150px),760px);
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 9%,#000 91%,transparent);
  mask-image:linear-gradient(to bottom,transparent,#000 9%,#000 91%,transparent);
}
.hero-full .hwall{max-width:none}
.hwall-in{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;height:100%;
  transform:rotate(-7deg) scale(1.22);transform-origin:50% 50%;
}
.hcol{min-width:0}
.hcol-t{will-change:transform}
.hcard{
  position:relative;margin:0 0 14px;border-radius:14px;overflow:hidden;
  background:var(--panel);border:1px solid rgba(255,255,255,.10);
  box-shadow:0 24px 60px -30px rgba(0,0,0,.8);
}
.hcard>img,.hcard>video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
/* same contract as .vcard and .gstk-c: a clip covers its poster only once it is
   really playing, never merely because a src has been set */
.hcard>video{opacity:0;transition:opacity .5s ease}
.hcard>video.is-playing{opacity:1}
.hcard>video[controls]{opacity:1;background:#0C0F1C}
.hcard-l{
  position:absolute;z-index:3;left:8px;top:8px;padding:3px 8px;border-radius:var(--r-pill);
  background:rgba(8,10,18,.62);backdrop-filter:blur(7px);border:1px solid rgba(255,255,255,.16);
  font-family:'Space Grotesk',sans-serif;font-size:.625rem;font-weight:600;
  letter-spacing:.02em;color:#fff;white-space:nowrap;
}
.hcard::after{
  content:"";position:absolute;inset:0;z-index:2;border-radius:inherit;pointer-events:none;
  background:rgba(5,7,14,0);box-shadow:inset 0 0 0 0 rgba(255,255,255,0);
  transition:background .4s var(--ease),box-shadow .4s var(--ease);
}
@media (hover:hover){
  .hwall:hover .hcard::after{background:rgba(5,7,14,.68)}
  .hwall .hcard:hover::after{background:rgba(5,7,14,0);box-shadow:inset 0 0 0 1px rgba(255,255,255,.5)}
  .hcard{transition:box-shadow .4s var(--ease),transform .4s var(--ease)}
  .hwall .hcard:hover{
    z-index:4;
    box-shadow:0 0 34px -2px var(--accent),0 0 100px -10px rgba(255,46,126,.55),0 24px 60px -30px rgba(0,0,0,.8);
  }
}
@media (prefers-reduced-motion:no-preference){
  .hcol-t{animation:hwall-up var(--dur,48s) linear infinite}
  .hcol-down .hcol-t{animation-name:hwall-down}
  @keyframes hwall-up{from{transform:translate3d(0,0,0)}to{transform:translate3d(0,-50%,0)}}
  @keyframes hwall-down{from{transform:translate3d(0,-50%,0)}to{transform:translate3d(0,0,0)}}
  /* the whole wall stops, not just the column under the pointer: that is what
     was asked for, and stopping one column while its neighbours run reads as a
     glitch rather than as attention */
  @media (hover:hover){
    .hwall:hover .hcol-t{animation-play-state:paused}
    .hwall .hcard:hover{transform:scale(1.035)}
  }
}
@media (max-width:1023px){
  .hwall{height:clamp(380px,72vw,560px)}
}
/* Two columns on a phone. At 350px three of them draw a 107px card, which is
   too small to read an ad -- and the wall's whole job is that you can. */
@media (max-width:639px){
  .hwall-in{grid-template-columns:repeat(2,minmax(0,1fr));transform:rotate(-5deg) scale(1.3)}
  .hcol:last-child{display:none}
}

/* ---- the homepage's animated ground ----

   The one thing that says "this is the homepage" before you have read a word.
   Three soft colour fields drifting behind the hero on long, unequal cycles, so
   the ground moves without ever arriving anywhere; alternate, so there is no
   jump at the loop. Only transform animates -- the blur is applied once and the
   layer is composited after that, which is what keeps three 860px blurred
   circles cheap.

   Nowhere else on the site: the subpage heroes are the same full screen without
   it, which is the difference the homepage gets to keep.

   It is decoration with no meaning, so it is aria-hidden in the markup and
   entirely off under prefers-reduced-motion -- the colours stay, the movement
   stops, which reads as a designed gradient rather than as something missing. */
.aur{position:absolute;inset:0;overflow:hidden;pointer-events:none}
.hero>.aur{z-index:0}
.aur>span{
  position:absolute;display:block;border-radius:50%;
  width:min(58vw,860px);aspect-ratio:1;filter:blur(76px);opacity:.62;
}
.aur>span:nth-child(1){top:-26%;left:-16%;background:radial-gradient(circle,rgba(255,46,126,.58),rgba(255,46,126,0) 66%)}
.aur>span:nth-child(2){bottom:-32%;right:-10%;background:radial-gradient(circle,rgba(124,77,255,.52),rgba(124,77,255,0) 66%)}
.aur>span:nth-child(3){
  top:18%;right:24%;width:min(40vw,580px);opacity:.5;
  background:radial-gradient(circle,rgba(56,110,255,.5),rgba(56,110,255,0) 64%);
}
@media (prefers-reduced-motion:no-preference){
  .aur>span{will-change:transform}
  .aur>span:nth-child(1){animation:aur-1 19s var(--ease-in-out) infinite alternate}
  .aur>span:nth-child(2){animation:aur-2 24s var(--ease-in-out) infinite alternate}
  .aur>span:nth-child(3){animation:aur-3 16s var(--ease-in-out) infinite alternate}
}
@keyframes aur-1{from{transform:translate3d(0,0,0) scale(1)}to{transform:translate3d(21vw,15vh,0) scale(1.22)}}
@keyframes aur-2{from{transform:translate3d(0,0,0) scale(1.12)}to{transform:translate3d(-18vw,-13vh,0) scale(.86)}}
@keyframes aur-3{from{transform:translate3d(0,0,0) scale(.92)}to{transform:translate3d(-16vw,18vh,0) scale(1.3)}}

/* Full-screen hero: the first screen is the hero and nothing else. min-height,
   not height, so copy that outgrows the viewport pushes the section taller
   instead of being clipped; svh rather than vh so a mobile URL bar sliding away
   does not make it jump. The header is in flow above, hence the subtraction.

   The square media is capped by what is left of the screen after the hero's own
   padding, so the picture can never be the reason the hero spills past one
   screen. Below the two-column breakpoint the cap comes off and the stack flows
   at its natural height. */
.hero-full{
  min-height:calc(100vh - var(--nav-h));min-height:calc(100svh - var(--nav-h));
  padding-top:clamp(24px,3.2vw,52px);padding-bottom:clamp(28px,3.4vw,58px);
  display:grid;align-content:center;
}
/* what the board may occupy: the screen, less the header above it, less this
   section's own padding (110) and the row's top margin (18). Rounded up to 142
   for slack — and if the sum is ever wrong the hero simply grows, because
   min-height does not clip. */
.hero-full .anim-media{width:100%;max-width:min(100%,calc(100svh - var(--nav-h) - 142px));margin-left:auto}
@media (max-width:1023px){
  .hero-full{min-height:0;padding-top:clamp(28px,4.6vw,44px);padding-bottom:clamp(38px,5vw,56px)}
  .hero-full .anim-media{max-width:min(100%,520px);margin-inline:auto}
}
.hero-h1{max-width:min(1000px,100%)}
.hero-h1-md{font-size:clamp(1.95rem,3.5vw,2.95rem);line-height:1.08;letter-spacing:-.028em}
.hero-lower{display:grid;grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr);gap:clamp(30px,4.5vw,60px);align-items:center;margin-top:clamp(26px,3.4vw,44px)}
.hero-copy{display:grid;gap:22px;justify-items:start;max-width:400px;padding-top:6px}
.hero-copy .row{gap:10px}
.hero .split-a{grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr)}
@media (max-width:1023px){
  .hero-lower{grid-template-columns:1fr;gap:34px}
  .hero-copy{max-width:56ch}
}

/* one input, many outputs */
.outrail{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;align-items:start;max-width:640px;margin-left:auto;width:100%}
.outrail>:last-child{padding-top:clamp(0px,2.4vw,30px)}
.outrail-col{display:grid;gap:12px}
@media (max-width:479px){.outrail{gap:8px}}

/* hero entrance + the cycling output tile */
@media (prefers-reduced-motion:no-preference){
  .anim>*{animation:rise .85s var(--ease) both}
  .anim>:nth-child(1){animation-delay:.05s}
  .anim>:nth-child(2){animation-delay:.16s}
  .anim>:nth-child(3){animation-delay:.27s}
  .anim>:nth-child(4){animation-delay:.38s}
  .anim-media{animation:rise 1s var(--ease) .2s both}
  @keyframes rise{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:none}}
}
/* one frame, four real outputs, crossfading */
.cycle{position:relative;isolation:isolate}
.cycle>*+*{position:absolute;inset:0}
.cycle>*+* .frame{height:100%}
@media (prefers-reduced-motion:no-preference){
  .cycle>*{opacity:0;animation:cyc 20s infinite both}
  .cycle>*:nth-child(1){animation-delay:0s}
  .cycle>*:nth-child(2){animation-delay:5s}
  .cycle>*:nth-child(3){animation-delay:10s}
  .cycle>*:nth-child(4){animation-delay:15s}
  @keyframes cyc{0%{opacity:0}2%,23%{opacity:1}27%,100%{opacity:0}}
}
@media (prefers-reduced-motion:reduce){
  .cycle>*+*{display:none}
}

/* ---------- media frames ---------- */
.frame{
  position:relative;border-radius:var(--r-card);overflow:hidden;
  background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);
}
.frame>img,.frame>video{width:100%;height:100%;object-fit:cover}
.frame-9x16{aspect-ratio:9/16}
.frame-4x5{aspect-ratio:4/5}
.frame-1x1{aspect-ratio:1}
.frame-16x9{aspect-ratio:16/9}
.frame-3x2{aspect-ratio:3/2}
.frame-21x9{aspect-ratio:21/9}
.frame-2x1{aspect-ratio:2/1}
.frame-auto{aspect-ratio:auto}
.frame-auto>img{height:auto}
.frame-contain{background:#fff}
.frame-contain>img{object-fit:contain}
/* A contained picture mattes to the surface it is sitting on. White is right
   inside a light section -- a screenshot of an app arrives on white anyway --
   and wrong on a dark band, where the letterbox becomes the brightest thing in
   the header and the eye is drawn to the empty part of the slot. */
.s-dark .frame-contain,.phead .frame-contain{background:rgba(255,255,255,.045)}
.cap{margin-top:10px;font-size:.8125rem;color:var(--fg-3)}
.shot{border-radius:var(--r-card);border:1px solid var(--line-2);overflow:hidden;box-shadow:var(--shadow-lg);background:#fff}
.shot img{width:100%}
.mgrid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
.mgrid-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.mstack{display:grid;gap:12px}
@media (max-width:479px){.mgrid-3{grid-template-columns:repeat(2,minmax(0,1fr))}}
/* The three API boards go one-up on a phone rather than two: .mgrid-3's two
   columns drew them at 167px, which is too small to read an API panel and an
   output side by side, and three cards in two columns leaves a hole anyway. */
@media (max-width:559px){.eng3{grid-template-columns:1fr}}
/* The models under an area: name left, maker right, one line each. Nothing
   else on the row -- the area's own copy says what comes back, and a sentence
   per engine is what made the first version of this section a wall of type.
   Hairlines rather than cards, so six of them read as one list. */
.mlist{list-style:none;margin:0;padding:0;display:grid}
.mlist>li{
  display:flex;justify-content:space-between;align-items:baseline;gap:10px;
  padding:8px 0;border-top:1px solid var(--line);font-size:.9375rem;
}
.mlist>li>b{font-family:'Space Grotesk',sans-serif;font-weight:600;letter-spacing:-.01em;color:var(--fg)}
.mlist>li>span{font-size:.75rem;color:var(--fg-3);text-align:right}
/* The homepage AI Avatar wall: six placements of one twin, four below 480.
   .mgrid-3 drops to two columns there, so six would be three rows -- and with
   media-first the wall sits above the headline, which made a screen and a half
   of faces before the argument started. Four still says "the same person in
   different places", which is the wall's whole job. The hidden two are lazy,
   so a phone does not download them. */
@media (max-width:479px){.avg>*:nth-child(n+5){display:none}}

/* ---------- marquee (max one per page) ---------- */
.marq{position:relative;overflow:hidden;padding-block:clamp(26px,3vw,40px);--speed:70s}
.marq::before,.marq::after{content:"";position:absolute;top:0;bottom:0;width:clamp(40px,9vw,160px);z-index:2;pointer-events:none}
.marq::before{left:0;background:linear-gradient(90deg,var(--bg),transparent)}
.marq::after{right:0;background:linear-gradient(270deg,var(--bg),transparent)}
.marq-track{display:flex;gap:14px;width:max-content;animation:slide var(--speed) linear infinite}
.marq:hover .marq-track{animation-play-state:paused}
.marq-item{height:clamp(150px,19vw,222px);flex:0 0 auto;border-radius:var(--r-card);overflow:hidden;border:1px solid var(--line);background:var(--panel)}
.marq-item img{height:100%;width:auto;max-width:none;object-fit:cover}
@keyframes slide{from{transform:translate3d(0,0,0)}to{transform:translate3d(-50%,0,0)}}
@media (prefers-reduced-motion:reduce){
  .marq-track{animation:none;overflow-x:auto}
}
@media (max-width:479px){.marq-track{gap:8px}}

/* ---------- cards, tiles, bento ---------- */
.cards{display:grid;gap:14px}
.c2{grid-template-columns:repeat(2,minmax(0,1fr))}
.c3{grid-template-columns:repeat(3,minmax(0,1fr))}
/* one card, at a width it can carry rather than the whole row */
.c1{grid-template-columns:minmax(0,min(560px,100%))}
.c4{grid-template-columns:repeat(4,minmax(0,1fr))}
/* Five cards: three, then two across the same width. In three columns the
   fifth leaves a hole where a sixth would be, and a hole in a grid reads as a
   card that failed to load. */
.c5{grid-template-columns:repeat(6,minmax(0,1fr))}
.c5>*{grid-column:span 2}
.c5>*:nth-child(4),.c5>*:nth-child(5){grid-column:span 3}
@media (max-width:1023px){
  .c3,.c4{grid-template-columns:repeat(2,minmax(0,1fr))}
  .c5{grid-template-columns:repeat(2,minmax(0,1fr))}
  .c5>*,.c5>*:nth-child(4),.c5>*:nth-child(5){grid-column:auto}
  .c5>*:last-child:nth-child(odd){grid-column:span 2}
}
@media (max-width:639px){
  .c2,.c3,.c4,.c5{grid-template-columns:1fr}
  .c5>*:last-child:nth-child(odd){grid-column:auto}
}

.card{
  position:relative;display:grid;gap:12px;align-content:start;
  padding:24px;border-radius:var(--r-card);
  background:var(--panel);border:1px solid var(--line);
  transition:border-color .2s ease,box-shadow .25s var(--ease),transform .25s var(--ease);
}
.s-light .card{box-shadow:0 1px 2px rgba(11,16,32,.04)}
a.card:hover{border-color:var(--line-2);box-shadow:var(--shadow);transform:translateY(-2px)}
.s-dark a.card:hover{background:var(--panel-2)}
.card-ico{width:38px;height:38px;display:grid;place-items:center;border-radius:11px;background:var(--accent-dim);color:var(--accent-fg)}
.card-ico .ico{width:19px;height:19px}
.card-flush{padding:0;overflow:hidden;gap:0}
.card-flush .card-body{padding:22px 24px 26px;display:grid;gap:10px}
.card-flush .frame{border:0;border-radius:0;box-shadow:none}
.card-arrow{position:absolute;top:22px;right:22px;color:var(--fg-3);transition:color .2s ease,transform .2s var(--ease)}
.card-arrow .ico{width:17px;height:17px}
a.card:hover .card-arrow{color:var(--accent-fg);transform:translate(2px,-2px)}

/* bento: exactly as many cells as there is content */
.bento{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:14px}
.b12{grid-column:span 12}.b7{grid-column:span 7}.b6{grid-column:span 6}.b5{grid-column:span 5}.b4{grid-column:span 4}
@media (max-width:1023px){.b7,.b5{grid-column:span 6}}
@media (max-width:767px){.b7,.b5,.b4{grid-column:span 6}}
@media (max-width:639px){.b7,.b6,.b5,.b4,.b12{grid-column:span 12}}

/* ---------- process rail ---------- */
.rail{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));border-top:1px solid var(--line)}
.step{padding:26px 22px 30px 0;display:grid;gap:10px;align-content:start;position:relative}
.step+.step{padding-left:22px;border-left:1px solid var(--line)}
.step::before{content:"";position:absolute;top:-1px;left:0;width:100%;height:2px;background:var(--accent);transform-origin:left}
.step+.step::before{left:-1px}
@media (prefers-reduced-motion:no-preference){
  .step::before{transform:scaleX(0);transition:transform .7s var(--ease)}
  .is-in .step::before{transform:scaleX(1)}
}
.step .num{font-size:.8125rem;color:var(--accent-fg);letter-spacing:.06em}
.rail-3{grid-template-columns:repeat(3,minmax(0,1fr))}
@media (min-width:1024px){.rail-3 .step:nth-child(3n+1){border-left:0;padding-left:0}}
@media (max-width:1023px){
  .rail{grid-template-columns:repeat(2,minmax(0,1fr))}
  .step,.step+.step{padding:22px 18px 24px;border-left:1px solid var(--line)}
  .step:nth-child(odd){border-left:0}
  .rail-3 .step:nth-child(3n+1){border-left:1px solid var(--line)}
  .rail-3 .step:nth-child(odd){border-left:0}
}
@media (max-width:639px){
  .rail{grid-template-columns:1fr;border-top:0}
  .step,.step+.step{border-left:0;border-top:1px solid var(--line);padding:20px 0}
  .rail-3 .step:nth-child(3n+1){border-left:0}
}

/* ---------- editorial roster ---------- */
.roster{display:grid;gap:clamp(26px,3vw,40px);grid-template-columns:repeat(3,minmax(0,1fr))}
@media (max-width:767px){.roster{grid-template-columns:1fr}}
.rgroup{display:grid;gap:2px}
.rrow{display:grid;gap:3px;padding:13px 0;border-top:1px solid var(--line)}
.rrow:first-of-type{border-top:1px solid var(--line-2)}
.rname{font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:1.0625rem;letter-spacing:-.015em;color:var(--fg)}
.rwhat{font-size:.875rem;color:var(--fg-3);line-height:1.45}

/* ---------- hairline list, workflow list ---------- */
.grid-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0 clamp(20px,3vw,40px)}
.gl-item{display:flex;gap:10px;align-items:flex-start;padding:13px 0;border-top:1px solid var(--line);font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:500;letter-spacing:-.01em;color:var(--fg)}
.gl-item .ico{width:15px;height:15px;color:var(--accent-fg);margin-top:4px}
@media (max-width:767px){.grid-list{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:479px){.grid-list{grid-template-columns:1fr}}

.wf{display:grid}
.wf li{display:grid;grid-template-columns:auto minmax(0,1fr);gap:0 16px;padding:18px 0;border-top:1px solid var(--line)}
.wf li:last-child{border-bottom:1px solid var(--line)}
.wf .num{font-size:.8125rem;color:var(--accent-fg);padding-top:4px;letter-spacing:.06em}
.wf h3{font-family:'Space Grotesk',sans-serif;font-size:1.0625rem;font-weight:600;letter-spacing:-.015em;color:var(--fg)}
.wf p{color:var(--fg-2);font-size:.9375rem;line-height:1.5;grid-column:2}

/* ---------- chips, tags, status ---------- */
.chips{display:flex;flex-wrap:wrap;gap:8px}
.chip{display:inline-flex;align-items:center;gap:7px;padding:7px 13px;border-radius:var(--r-chip);background:var(--panel-2);border:1px solid var(--line);font-size:.875rem;color:var(--fg-2)}
.s-dark .chip{background:rgba(255,255,255,.05)}
.chip .ico{width:14px;height:14px;color:var(--accent-fg)}

/* ---------- feature groups ---------- */
.fgroups{display:grid;gap:14px;grid-template-columns:repeat(2,minmax(0,1fr))}
@media (max-width:767px){.fgroups{grid-template-columns:1fr}}
.fgroup{padding:26px;border-radius:var(--r-card);background:var(--panel);border:1px solid var(--line)}
.s-light .fgroup{box-shadow:0 1px 2px rgba(11,16,32,.04)}
.fgroup>h3{display:flex;align-items:center;gap:11px;margin-bottom:6px}
/* Three groups in two columns leave the third one alone beside an empty cell.
   It takes the whole row instead and splits its own items in two, so the row
   is full and the reading width stays the same as the cards above it. */
.fgroups>.fgroup:last-child:nth-child(odd){grid-column:1/-1}
.fgroups>.fgroup:last-child:nth-child(odd)>.fgroup-items{
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 clamp(22px,3vw,44px);
}
@media (max-width:759px){
  .fgroups>.fgroup:last-child:nth-child(odd)>.fgroup-items{grid-template-columns:1fr}
}
.fitem{display:grid;gap:3px;padding:15px 0;border-bottom:1px solid var(--line)}
.fitem:last-child{border-bottom:0;padding-bottom:0}
.fitem-h{display:flex;align-items:center;gap:9px;flex-wrap:wrap}

/* ---------- splits ---------- */
.split{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(30px,4.4vw,68px);align-items:center}
.split-a{grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr)}
.split-b{grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr)}
/* for a split whose picture is carrying the argument: the copy keeps a workable
   measure and everything else goes to the media */
.split-w{grid-template-columns:minmax(0,.78fr) minmax(0,1.22fr)}
.split>.stack{align-content:center}
@media (max-width:939px){
  .split,.split-a,.split-b{grid-template-columns:1fr;gap:32px}
  .split .media-first{order:-1}
}

/* ---------- panels ---------- */
.pane{padding:clamp(28px,3.4vw,46px);border-radius:var(--r-card);background:var(--panel-2);border:1px solid var(--line)}
.s-dark .pane{background:linear-gradient(160deg,var(--panel),#0E1122 62%)}
.pane-accent{border-color:rgba(255,46,126,.28);background:linear-gradient(150deg,rgba(255,46,126,.08),var(--panel-2) 46%)}
.plist{display:grid;gap:11px}
.plist li{display:flex;gap:10px;align-items:flex-start;color:var(--fg-2);font-size:.9375rem;line-height:1.5}
.plist .ico{width:16px;height:16px;color:var(--accent-fg);margin-top:4px}

/* ---------- quote ---------- */
.quote{display:grid;gap:16px;max-width:56ch}
.quote p{font-family:'Space Grotesk',sans-serif;font-size:clamp(1.15rem,1.9vw,1.5rem);line-height:1.35;letter-spacing:-.02em;color:var(--fg)}
.byline{display:flex;align-items:center;gap:11px}
.byline img{width:40px;height:40px;border-radius:50%;object-fit:cover}

/* ---------- pricing ---------- */
/* Four across: three priced plans and Custom, which is where the row is read
   left to right as the volume climbing. Two up before the cards get too narrow
   to hold a price, one up on a phone. */
.plans{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;align-items:start}
@media (max-width:1199px){.plans{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:719px){.plans{grid-template-columns:1fr}}
.plan{display:grid;gap:18px;align-content:start;padding:28px;border-radius:var(--r-card);background:var(--panel);border:1px solid var(--line)}
/* Head, price, numbers, button. Subgrid makes those four bands line up across
   the three cards, so a longer description on one plan does not offset every row
   beside it. It was five while the cards also carried the monthly averages. */
@supports (grid-template-rows:subgrid){
  @media (min-width:1200px){
    .plans{grid-template-rows:auto auto auto auto}
    .plan{grid-row:span 4;grid-template-rows:subgrid}
    /* Enterprise has no price band since 10 September 2026, so its three
       children left row four empty and its button 70px above the other three.
       Its list takes rows two and three instead -- which also stops a 157px
       list inflating the price row of all four cards by 95px. */
    .plan-cu .dims{grid-row:span 2}
  }
}
.s-light .plan{box-shadow:0 1px 2px rgba(11,16,32,.04)}
.plan-hi{position:relative;border-color:rgba(255,46,126,.32);background:linear-gradient(165deg,rgba(255,46,126,.07),var(--panel) 52%)}
.plan-hi::before{content:"";position:absolute;top:-1px;left:-1px;right:-1px;height:3px;border-radius:var(--r-card) var(--r-card) 0 0;background:var(--accent)}
.price{display:flex;align-items:baseline;gap:8px}
.price>span:not(.num){color:var(--fg-3);font-size:.9375rem}
.price .num{font-size:clamp(2.2rem,3.4vw,2.9rem);letter-spacing:-.035em;color:var(--fg)}
/* ---------- pricing card ----------
   Head, price, the four numbers that decide the plan, then what a month of that
   pool actually buys. The feature-by-feature comparison is the table below, so
   the card stays something you can read in one pass. */
.plan-head{display:grid;gap:7px}
.plan-head .small{color:var(--fg-2)}
/* The rows sit at the top of their band. The band is as tall as the tallest
   card's -- Enterprise lists six things it agrees with you -- and without this
   two rows shared that height between them and stood a hundred pixels apart. */
.spec{display:grid;align-content:start;border-top:1px solid var(--line)}
.spec>div{
  display:flex;align-items:baseline;justify-content:space-between;gap:14px;
  padding:11px 0;border-bottom:1px solid var(--line);
}
.spec dt{font-size:.875rem;color:var(--fg-2)}
.spec dd{
  font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:600;
  letter-spacing:-.01em;color:var(--fg);text-align:right;white-space:nowrap;
}
.plan .btn{width:100%}

/* ---------- the Enterprise card ----------
   No price and no rate on it: what it costs follows from the conversation. What
   it shows instead is the list of things that conversation decides, so "bespoke"
   is a set of specifics rather than a word. The dashed border says the same
   thing as the copy: this one is not a number yet. */
.plan-cu{border-style:dashed;border-color:var(--line-2);background:var(--panel-2)}
.plan-cu .price .num{font-size:clamp(1.5rem,2.2vw,1.9rem)}
.dims{display:grid;gap:8px;align-content:start;border-top:1px solid var(--line);padding-top:15px}
.dims-h{
  margin:0 0 2px;font-size:.6875rem;text-transform:uppercase;
  letter-spacing:.09em;color:var(--fg-3);
}
.dims-r{display:flex;align-items:center;gap:9px;font-size:.875rem;color:var(--fg-2)}
.dims-r .ico{width:15px;height:15px;flex:0 0 auto;color:var(--accent-fg)}


/* ---------- the agency credit model, drawn ----------
   One pool at the top and three accounts drawing on it, which is the whole
   arrangement in one look. Every number on it is the page's own illustration and
   the caption says so. */
.agc{margin:0;display:grid;justify-items:center;gap:16px}
.agc-pool{
  display:grid;justify-items:center;gap:2px;padding:20px 34px;
  border-radius:var(--r-card);border:1px solid rgba(255,255,255,.14);
  background:linear-gradient(180deg,rgba(255,255,255,.07),rgba(255,255,255,.02));
}
.agc-who{font-size:.75rem;text-transform:uppercase;letter-spacing:.09em;color:rgba(255,255,255,.45)}
.agc-n{
  font-family:'Space Grotesk',sans-serif;font-size:clamp(1.9rem,3.4vw,2.6rem);
  font-weight:600;letter-spacing:-.035em;color:#fff;
}
.agc-l{font-size:.8125rem;color:rgba(255,255,255,.5)}
.agc-flow{
  display:inline-flex;align-items:center;gap:7px;
  font-size:.75rem;text-transform:uppercase;letter-spacing:.09em;color:var(--accent-fg);
}
.agc-flow .ico{width:15px;height:15px;transform:rotate(90deg)}
.agc-cl{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;width:100%;max-width:780px}
.agc-c{
  display:grid;gap:5px;padding:16px 18px;border-radius:14px;
  border:1px solid rgba(255,255,255,.11);background:rgba(255,255,255,.04);
}
.agc-ct{font-size:.8125rem;color:rgba(255,255,255,.55)}
.agc-cn{
  font-family:'Space Grotesk',sans-serif;font-size:1.375rem;font-weight:600;
  letter-spacing:-.03em;color:#fff;
}
.agc-cd{
  display:flex;align-items:center;gap:7px;margin-top:3px;
  font-size:.75rem;color:var(--accent-fg);
}
.agc-cd .ico{width:14px;height:14px}
.agc .cap{color:rgba(255,255,255,.42);text-align:center;max-width:56ch}
@media (prefers-reduced-motion:no-preference){
  .agc-c{animation:agc-in .5s both;animation-delay:calc(.15s + var(--i) * .12s)}
}
@keyframes agc-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@media (max-width:719px){
  .agc-cl{grid-template-columns:1fr;max-width:340px}
}

/* ---------- the on-request tier ----------
   It sits under the three priced plans, in the same band, because somebody
   comparing plans should find it without being sent to another section. It is
   not in the grid with them: it has no pool and no per-credit rate, so it has
   nothing to line up with, and its own two columns say more with the width. */
.plan-wide{gap:clamp(24px,3.4vw,48px)}
@media (min-width:860px){
  .plan-wide{grid-template-columns:minmax(0,1fr) minmax(0,.9fr);align-items:center}
}
.plan-wide .price .num{font-size:clamp(1.7rem,2.4vw,2.1rem)}
.plan-wide .btn{width:auto}
/* The top of the range gets the dark panel the rest of the site uses to mark the
   thing that matters, and a tag beside the eyebrow so "full version" is read
   before the copy explaining it. */
.plan-top{
  border:1px solid rgba(255,255,255,.14);
  background:
    radial-gradient(120% 140% at 88% 0%,rgba(255,46,126,.20),transparent 58%),
    linear-gradient(165deg,#151a2e,var(--bg) 60%);
  box-shadow:0 26px 60px -34px rgba(11,16,32,.5);
}
.pw-tag{
  display:inline-block;margin-left:10px;padding:3px 9px;border-radius:999px;
  background:var(--accent);color:var(--accent-ink);
  font-size:.625rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  font-family:'Space Grotesk',sans-serif;
}
.pw-l>*{margin-top:0}
.pw-r{min-width:0}
@media (min-width:1100px){
  /* seven items read better in two columns than as one long list */
  .pw-r .plist{grid-template-columns:repeat(2,minmax(0,1fr));column-gap:26px}
}
@media (max-width:939px){.plan .btn{width:auto;justify-self:start}}

.tbl-scroll{overflow-x:auto;border-radius:var(--r-card);border:1px solid var(--line);background:var(--panel)}
/* The box takes focus because it scrolls sideways, so a keyboard can reach the
   right-hand columns. The global focus rule squares the corners; this keeps the
   card's own radius while it is focused. */
.tbl-scroll:focus-visible{border-radius:var(--r-card);outline-offset:2px}
table.cmp{width:100%;border-collapse:collapse;min-width:680px;font-size:.9375rem;color:var(--fg)}
table.cmp th,table.cmp td{padding:14px 18px;text-align:left;vertical-align:top}
table.cmp thead th{font-family:'Space Grotesk',sans-serif;font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:var(--fg-3);border-bottom:1px solid var(--line-2);white-space:nowrap}
table.cmp tbody tr+tr td{border-top:1px solid var(--line)}
table.cmp td:not(:first-child),table.cmp th:not(:first-child){text-align:center;color:var(--fg-2)}
/* A two-column table has nothing to centre between: the label reads from the
   left and the number from the right, and it needs no minimum width because
   there are no plan columns to keep apart. */
table.cmp.cmp-2{min-width:0}
table.cmp.cmp-2 td:not(:first-child),table.cmp.cmp-2 th:not(:first-child){text-align:right}
table.cmp.cmp-2 td:first-child{width:100%}
table.cmp .grp td{background:var(--panel-2);font-family:'Space Grotesk',sans-serif;font-weight:600;color:var(--fg);font-size:.875rem;text-transform:uppercase;letter-spacing:.09em}
/* The mark is quiet on purpose: it says "there" without shouting, and the dash
   next to it says "not there" in the same breath. */
table.cmp .yes,table.cmp .no{text-align:center;width:1%;white-space:nowrap}
table.cmp .yes .tick{
  display:inline-grid;place-items:center;width:22px;height:22px;border-radius:50%;
  background:rgba(255,46,126,.12);color:var(--accent-fg);
  font-size:.8125rem;line-height:1;
}
table.cmp .no{color:var(--fg-3);font-size:1rem}
/* An offer rather than a tick, so it reads as one */
table.cmp .avail{
  display:inline-block;padding:3px 10px;border-radius:99px;
  border:1px solid var(--accent);background:rgba(255,46,126,.1);
  font-family:'Space Grotesk',sans-serif;font-size:.75rem;font-weight:600;
  letter-spacing:.01em;color:var(--accent-fg);white-space:nowrap;
}
.money{font-variant-numeric:tabular-nums;font-family:var(--mono);font-size:.875rem}

/* ---------- faq ---------- */
/* Full width, and closer together: the rows carry the whole measure of the
   page and the answer keeps its own reading width inside them. */
.faq{display:grid}
.faq details{border-top:1px solid var(--line)}
.faq details:last-of-type{border-bottom:1px solid var(--line)}
.faq summary{display:flex;justify-content:space-between;align-items:center;gap:18px;padding:15px 2px;cursor:pointer;font-family:'Space Grotesk',sans-serif;font-size:1.0625rem;font-weight:600;letter-spacing:-.015em;list-style:none;color:var(--fg)}
.faq summary::-webkit-details-marker{display:none}
.faq summary .ico{color:var(--fg-3);transition:transform .22s var(--ease);flex:0 0 auto}
.faq details[open] summary{padding-bottom:8px}
.faq details[open] summary .ico{transform:rotate(45deg);color:var(--accent-fg)}
.faq .ans{padding:0 2px 17px;max-width:76ch;color:var(--fg-2)}
.faq .ans>*+*{margin-top:8px}

/* ---------- code ---------- */
.code{border-radius:var(--r-card);border:1px solid var(--line);background:var(--code-bg);overflow:hidden}
.code-hd{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid rgba(255,255,255,.09);font-family:var(--mono);font-size:.75rem;color:#8A92AC}
.code pre{margin:0;padding:18px;overflow-x:auto;font-family:var(--mono);font-size:.8125rem;line-height:1.6;color:var(--code-fg)}
.code .s{color:#8FD6B4}
.code .c{color:#8A92AC}

/* ---------- article prose ---------- */
.prose{max-width:68ch;display:grid;gap:20px}
.prose h2{font-family:'Space Grotesk',sans-serif;font-size:clamp(1.35rem,2.1vw,1.65rem);font-weight:600;letter-spacing:-.025em;line-height:1.2;margin-top:20px;color:var(--fg)}
.prose h3{font-family:'Space Grotesk',sans-serif;font-size:1.125rem;font-weight:600;letter-spacing:-.02em;margin-top:12px;color:var(--fg)}
.prose p{color:var(--fg-2)}
/* The marker is positioned, not a flex item. A list item was a flex row, and a
   CRM article that opens a bullet with "<strong>Prompt adherence:</strong> then
   the rest" put the bold part and the rest in two columns side by side: the
   lead-in wrapped over two lines while its own sentence started up and to the
   right of it. Inline flow keeps a sentence a sentence. */
.prose ul{display:grid;gap:9px}
.prose ul li{position:relative;padding-left:19px;color:var(--fg-2)}
.prose ul li::before{
  content:"";position:absolute;left:0;top:.6em;
  width:5px;height:5px;border-radius:50%;background:var(--accent);
}
.prose ol{display:grid;gap:9px;counter-reset:s}
.prose ol li{position:relative;padding-left:34px;color:var(--fg-2);counter-increment:s}
.prose ol li::before{
  content:counter(s,decimal-leading-zero);position:absolute;left:0;top:.15em;
  font-family:'Space Grotesk',sans-serif;font-size:.8125rem;color:var(--accent-fg);
}
.prose blockquote{border-left:2px solid var(--accent);padding-left:20px;font-family:'Space Grotesk',sans-serif;font-size:1.125rem;letter-spacing:-.015em;color:var(--fg)}
/* Everything the CRM's editor can send. site/lib/sanitize.mjs decides what
   survives the import; this decides how it looks once it has. An article body
   is now HTML from a rich editor, so these are not decoration. */
.prose h4{font-family:'Space Grotesk',sans-serif;font-size:1rem;font-weight:600;letter-spacing:-.015em;margin-top:8px;color:var(--fg)}
.prose strong,.prose b{color:var(--fg);font-weight:600}
.prose em,.prose i{font-style:italic}
.prose a{color:var(--fg);text-decoration:underline;text-decoration-color:var(--accent);text-underline-offset:3px;text-decoration-thickness:1.5px}
.prose a:hover{color:var(--accent-fg)}
.prose img{display:block;width:100%;max-width:100%;height:auto;border-radius:var(--r-card);border:1px solid var(--line);background:var(--panel)}
.prose figure{display:grid;gap:10px;margin:0}
.prose figcaption{font-size:.8125rem;line-height:1.45;color:var(--fg-3)}
.prose hr{border:0;border-top:1px solid var(--line);margin:4px 0}
.prose code{font-family:var(--mono);font-size:.875em;padding:2px 6px;border-radius:6px;border:1px solid var(--line);background:var(--panel);color:var(--fg)}
.prose pre{overflow-x:auto;padding:16px 18px;border-radius:var(--r-card);border:1px solid var(--line);background:var(--panel)}
.prose pre code{padding:0;border:0;background:none;font-size:.8125rem;line-height:1.6}
.meta{display:flex;flex-wrap:wrap;gap:14px;align-items:center;color:var(--fg-3);font-size:.875rem}
/* The "this is a translation, the English binds" line on a translated legal
   page. Set apart from the lede above it and the document below it, because it
   is neither: it is a statement about the page, and a reader has to be able to
   tell that at a glance. */
.lgl-note{
  margin-top:18px;padding:12px 16px;
  border-left:3px solid var(--fg-3);border-radius:0 8px 8px 0;
  background:rgba(17,20,34,.045);
  color:var(--fg-2);font-size:.9375rem;line-height:1.5;max-width:62ch;
}
.crumb{display:flex;flex-wrap:wrap;gap:9px;align-items:center;font-size:.8125rem;color:var(--fg-3)}
.crumb a:hover{color:var(--fg-2)}
.crumb .ico{width:13px;height:13px;opacity:.5}

/* ---------- segmented control ---------- */
.seg{position:relative;display:inline-flex;gap:2px;padding:3px;border-radius:var(--r-pill);background:var(--panel-2);border:1px solid var(--line)}
.seg button{position:relative;z-index:1;padding:8px 15px;border:0;border-radius:var(--r-pill);background:none;cursor:pointer;color:var(--fg-3);font-family:'Space Grotesk',sans-serif;font-size:.875rem;font-weight:600;white-space:nowrap;transition:background-color .18s ease,color .18s ease}
.seg button:hover{color:var(--fg)}
.seg button.is-on{background:var(--accent);color:var(--accent-ink)}
.seg .off{font-size:.75rem;opacity:.75;margin-left:5px}

/* ---------- the billing period, centred and given some weight ----------
   One control on its own line, with the sentence that explains the choice
   directly under it, so the discount and its condition are read together. */
.segwrap{display:grid;justify-items:center;gap:14px;text-align:center}
/* Pricing header: the title on the left, the billing switch on the right, so
   the four plans and their buttons land in the first screen. Measured before:
   the switch on its own row held the buttons 126px under a 900px fold. */
.prhd{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:clamp(18px,2.6vw,44px);align-items:end}
.prhd .lede{max-width:58ch}
.prhd .segwrap{justify-items:end;text-align:right}
.prhd .seg-note{max-width:34ch}
@media (max-width:1099px){
  .prhd{grid-template-columns:1fr;gap:24px}
  .prhd .segwrap{justify-items:center;text-align:center}
  .prhd .seg-note{max-width:56ch}
}
.seg-note{
  margin:0;max-width:56ch;min-height:2.6em;
  font-size:.9375rem;line-height:1.5;color:var(--fg-2);
  /* two balanced lines rather than one long one and an orphan; the reserved
     height stops the cards jumping when the sentence changes length */
  text-wrap:balance;
}

/* The larger control: a track, a pill that slides between the options, and the
   saving shown as a chip rather than as small print. The pill is positioned by
   script from the active button; until it is, the active button carries its own
   background, so the control is never left without an indicator. */
.seg-lg{
  gap:4px;padding:5px;border-color:var(--line-2);
  box-shadow:inset 0 1px 2px rgba(11,16,32,.05);
}
.seg-lg button{
  display:inline-flex;align-items:center;gap:8px;
  padding:11px 20px;font-size:.9375rem;color:var(--fg-2);
  transition:color .2s var(--ease);
}
.seg-lg button:hover{color:var(--fg)}
.seg-lg .off{
  margin:0;padding:3px 8px;border-radius:999px;
  font-size:.6875rem;font-weight:700;letter-spacing:.02em;opacity:1;
  background:var(--accent-dim);color:var(--accent-fg);
  transition:background-color .2s var(--ease),color .2s var(--ease);
}
.seg-thumb{
  position:absolute;top:5px;left:0;height:calc(100% - 10px);width:0;
  border-radius:var(--r-pill);background:var(--accent);
  box-shadow:0 4px 14px -6px rgba(255,46,126,.6);
  opacity:0;pointer-events:none;
}
/* Once the pill has been measured it takes over, and the buttons go flat. */
.seg-live .seg-thumb{opacity:1;transition:transform .32s var(--ease),width .32s var(--ease)}
.seg-live button.is-on{background:none}
.seg-live button.is-on,.seg-lg button.is-on{color:var(--accent-ink)}
.seg-live button.is-on .off,.seg-lg button.is-on .off{background:rgba(255,255,255,.22);color:var(--accent-ink)}
@media (prefers-reduced-motion:reduce){
  .seg-live .seg-thumb{transition:none}
}

/* On a 360px phone the three labels and their chips do not fit on one line, so
   the control becomes a column and each option keeps its full width. */
@media (max-width:560px){
  .seg-lg{flex-direction:column;width:100%;max-width:320px;border-radius:var(--r-card)}
  .seg-lg button{justify-content:space-between;width:100%;border-radius:calc(var(--r-card) - 4px)}
  /* a pill that slides down a column reads as a mistake: the button keeps it */
  .seg-lg .seg-thumb{display:none}
  .seg-live.seg-lg button.is-on{background:var(--accent)}
}

/* A legal document: title, the line that says what it is, its dates, then the
   text, all in one band. The dates need air above them or they read as a third
   line of the lede; the text needs more, because that is where the document
   starts. The adjacency rule rather than a class, because two of these four
   documents carry their own dates line and it arrives without one. */
.lede+.meta{margin-top:18px}
.lg-body{margin-top:clamp(28px,3.4vw,44px)}

/* ---------- forms ---------- */
.form{display:grid;gap:16px}
.form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
@media (max-width:639px){.form-grid{grid-template-columns:1fr}}
.field{display:grid;gap:7px}
.field label{font-family:'Space Grotesk',sans-serif;font-size:.875rem;font-weight:600;color:var(--fg)}
.field .req{color:var(--accent-fg)}
.field input,.field select,.field textarea{
  width:100%;padding:12px 14px;border-radius:var(--r-chip);
  background:var(--bg);border:1px solid var(--line-2);color:var(--fg);font-size:.9375rem;
  transition:border-color .18s ease,background-color .18s ease;
}
.s-dark .field input,.s-dark .field select,.s-dark .field textarea{background:#0C0F1C}
.field textarea{resize:vertical;min-height:118px}
.field input::placeholder,.field textarea::placeholder{color:var(--fg-3);opacity:1}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--accent)}
.field .hint{font-size:.8125rem;color:var(--fg-3)}
/* Consent, asked rather than assumed. Two boxes, both required: the form does
   not submit until they are ticked, and the message says which one is missing.
   Nothing about them is sent to the database -- the contacts table has no column
   for it, so this is a gate on the page, not a record. */
.consents{display:grid;gap:11px}
.cbx{display:grid;grid-template-columns:auto minmax(0,1fr);gap:11px;align-items:start;cursor:pointer}
.cbx input{
  appearance:none;-webkit-appearance:none;margin:1px 0 0;flex:0 0 auto;
  width:19px;height:19px;border-radius:5px;cursor:pointer;
  border:1.5px solid var(--line-2);background:var(--bg);
  transition:border-color .16s ease,background-color .16s ease;
}
.cbx input:hover{border-color:var(--fg-3)}
.cbx input:checked{
  border-color:var(--accent);background:var(--accent);
  /* the tick is drawn, so there is no icon request for a 19px box */
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 13l4 4L19 7'/%3E%3C/svg%3E");
  background-size:14px;background-position:center;background-repeat:no-repeat;
}
.cbx input[aria-invalid="true"]{border-color:var(--accent);background:var(--accent-dim)}
.cbx>span{font-size:.875rem;line-height:1.5;color:var(--fg-2)}
.cbx a{color:var(--fg);text-decoration:underline;text-decoration-color:var(--accent);text-underline-offset:3px}
.cbx a:hover{color:var(--accent-fg)}

.form-note{font-size:.8125rem;color:var(--fg-3);max-width:52ch}
.hp{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}
.form-status{padding:13px 15px;border-radius:var(--r-chip);border:1px solid rgba(255,46,126,.45);background:rgba(255,46,126,.08);color:var(--accent-fg);font-size:.9375rem}
.form-done{padding:26px;border-radius:var(--r-card);border:1px solid rgba(255,46,126,.3);background:linear-gradient(160deg,rgba(255,46,126,.08),var(--panel) 55%)}
.form-done h3{font-family:'Space Grotesk',sans-serif;font-size:1.25rem;font-weight:600;letter-spacing:-.02em;margin-bottom:8px;color:var(--fg)}

/* ---------- footer (dark chrome) ---------- */
.ft{border-top:1px solid rgba(255,255,255,.09);background:#080A11;color:#A9B0C6;padding-block:clamp(48px,5vw,68px) 28px}
.ft-top{display:grid;grid-template-columns:minmax(0,1.3fr) repeat(4,minmax(0,1fr));gap:32px}
@media (max-width:1023px){.ft-top{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:479px){.ft-top{grid-template-columns:1fr}}
.ft h4{font-family:'Space Grotesk',sans-serif;font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.12em;color:#8A92AC;margin-bottom:14px}
.ft ul{display:grid;gap:9px}
.ft a{color:#A9B0C6;font-size:.9375rem}
.ft a:hover{color:#FF63A0}
.ft-brand{display:grid;gap:14px;align-content:start;max-width:34ch}
.ft-brand img{height:26px;width:auto}
.ft-brand .small{color:#8A92AC}
.ft .link{color:#F3F4F8;border-color:rgba(255,255,255,.2)}
.ft .link:hover{color:#FF63A0}
.ft-util{
  margin-top:clamp(30px,3.6vw,46px);padding-top:22px;border-top:1px solid rgba(255,255,255,.09);
  display:flex;flex-wrap:wrap;gap:14px 40px;justify-content:space-between;
}
.ft-util ul{display:flex;flex-wrap:wrap;gap:8px 22px}
.ft-util a,.ft-btn{font-size:.875rem;color:#A9B0C6}
.ft-util a:hover,.ft-btn:hover{color:#FF63A0}
.ft-btn{background:none;border:0;padding:0;cursor:pointer;font-family:inherit;text-decoration:underline;text-underline-offset:3px;text-decoration-color:rgba(255,255,255,.25)}
/* The language switcher. Last in the utility row, because it is the thing a
   reader goes looking for rather than reads in passing: a person who wants
   their own language scans the bottom of the page for the word they know, and
   finds it in the same place on every page. Closed it shows that word -- the
   language you are reading -- and opens the other four above it.

   It is the header's control, reused whole: .nav-item + .nav-btn + .mega, the
   shape covely.js already drives. What has to be undone here is only what
   .nav-btn assumes about where it lives. That button is drawn for the white
   header -- slate text going near-black on hover, a chip-sized padding box and
   an accent underline that slides in underneath -- and none of that belongs in
   a utility line on a near-black ground, where the neighbouring links are grey
   going pink. So: the padding and the underline come off, and the colours
   become .ft-util's. The panel itself keeps its light surface, so the rows
   inside it stay dark and need the footer's own .ft a colour overridden back. */
.ft-lang{margin-left:auto;align-self:center;display:flex}
.ft-lang>.nav-btn{
  padding:0;gap:5px;
  font-family:inherit;font-size:.875rem;font-weight:400;color:#A9B0C6;
}
.ft-lang>.nav-btn::after{display:none}
.ft-lang>.nav-btn:hover,.ft-lang:hover>.nav-btn,.ft-lang:focus-within>.nav-btn{color:#FF63A0}
/* The chevron points at the panel, which here is above: so it rests pointing
   up and turns down when open, the exact mirror of the header's. There is no
   chevron-up in site/icons and there does not need to be -- .nav-item already
   rotates this one by 180 degrees on open, so starting it rotated inverts the
   pair for free, and the sprite stays one icon lighter. */
.ft-lang .ico:not(.flag){width:12px;height:12px;transform:rotate(180deg)}
.ft-lang:hover>.nav-btn .ico:not(.flag),.ft-lang:focus-within>.nav-btn .ico:not(.flag){transform:rotate(0deg)}
.ft-lang-now{font-family:'Space Grotesk',sans-serif;font-weight:500}
.ft .lang-list a{font-size:.9375rem;color:#4E566E}
.ft .lang-list a:hover{color:#0B1020}
.ft .lang-list a[aria-current]{color:#0B1020}
/* Below this the utility row wraps and the switcher goes to the left of its
   own line -- measured, the flip is exactly here -- so a panel anchored to its
   right edge would hang 65px off the left of a 390px screen. Anchor it to the
   left instead, which is where there is now room. */
@media (max-width:719px){
  .ft-lang{margin-left:0}
  .ft-lang .mega{left:0;right:auto}
}
.ft-bot{margin-top:22px;padding-top:22px;border-top:1px solid rgba(255,255,255,.09);display:flex;flex-wrap:wrap;gap:12px;justify-content:space-between;color:#767E99;font-size:.8125rem}

/* ---------- language invitation ----------
   A strip under the header offering this page in the reader's own language.

   Sticky in the flow, not fixed. Fixed was tried first and costs no layout
   shift, but it sits on top of whatever is directly under the header -- the
   breadcrumb on every subpage, the eyebrow line on the homepage -- and hides
   it for as long as the strip is up. In the flow nothing is ever covered; the
   price is one shift when the script unhides it, measured at 0.028 on the
   homepage at 1440 and 0.000 at 390, against a 0.1 budget, and only for a
   reader whose browser language is not the page's.

   top:var(--nav-h) parks it against the header once the page scrolls. Under
   the header (60) and the drawer (59), over everything else. */
.lb{
  position:sticky;top:var(--nav-h);z-index:58;
  background:#0C0F1C;border-bottom:1px solid rgba(255,255,255,.14);
  box-shadow:0 18px 40px -30px rgba(0,0,0,.9);
}
.lb[hidden]{display:none}
.lb-inner{max-width:1440px;margin:0 auto;padding:9px var(--gut) 10px}
/* Load-bearing, and the house pattern (see .cc[hidden]): a display rule beats
   the user agent's [hidden]. Every row but one stays hidden. */
.lb-row{display:flex;flex-wrap:wrap;align-items:center;gap:4px 18px;margin:0;font-size:.875rem;color:#E7E9F2}
.lb-row[hidden]{display:none}
.lb-go{color:#FF63A0;border-bottom:1px solid currentColor;padding-bottom:1px}
.lb-go:hover{color:#FFF}
.lb-no{background:none;border:0;padding:0;margin-left:auto;color:#A9B0C6;font:inherit;cursor:pointer}
.lb-no:hover{color:#FFF}
@media (max-width:639px){.lb-inner{padding:8px var(--gut) 9px}.lb-row{gap:2px 18px;font-size:.8125rem}}

/* ---------- cookie consent ---------- */
.cc{
  position:fixed;left:0;right:0;bottom:0;z-index:80;
  background:#0C0F1C;border-top:1px solid rgba(255,255,255,.14);
  box-shadow:0 -24px 60px -30px rgba(0,0,0,.9);
}
.cc[hidden]{display:none}
.cc-inner{
  max-width:1440px;margin:0 auto;padding:18px var(--gut) 20px;
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:14px 32px;align-items:center;
}
.cc-copy{max-width:74ch}
.cc-copy .small{margin-top:5px}
.cc-copy .link{display:inline;border-bottom-width:1px;padding-bottom:1px;font-size:inherit}
.cc-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}
.cc-prefs{grid-column:1/-1;display:grid;gap:12px;padding-top:16px;border-top:1px solid rgba(255,255,255,.10)}
.cc-prefs[hidden]{display:none}
.cc-row{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:start;cursor:pointer;max-width:74ch}
.cc-row input{width:18px;height:18px;margin-top:3px;accent-color:var(--accent)}
.cc-row .d4{display:block}
.cc-row .small{display:block;margin-top:2px}
.cc-row input:disabled{opacity:.6;cursor:not-allowed}
@media (max-width:767px){
  .cc-inner{grid-template-columns:1fr}
  .cc-actions{justify-content:flex-start}
  .cc-actions .btn{flex:1 1 auto}
}

/* ---------- reveal ---------- */
@media (prefers-reduced-motion:no-preference){
  .rv{opacity:0;transform:translateY(18px);transition:opacity .7s var(--ease),transform .7s var(--ease);transition-delay:calc(var(--i,0)*70ms)}
  .rv.is-in{opacity:1;transform:none}
}
.no-js .rv{opacity:1;transform:none}
/* and the wipe, which lives on the media now. .no-js .frame.rv is (0,3,0)
   against (0,2,0), so it wins wherever it sits in the file. */
.no-js .frame.rv>img,.no-js .frame.rv>video{clip-path:none}

/* the same escape for the reveals that are drawn rather than faded. .is-in can
   never coexist with .no-js: covely.js drops the class before anything adds it. */
.no-js .step::before,.no-js .tri-col::before{transform:scaleX(1)}
.no-js .fan-wires path:not(.pulse){stroke-dashoffset:0}

/* ---------- utilities ---------- */
.mt-0{margin-top:0}.mt-1{margin-top:10px}.mt-2{margin-top:20px}.mt-3{margin-top:32px}.mt-4{margin-top:clamp(34px,4vw,52px)}
.center{text-align:center;justify-items:center;margin-inline:auto}
.center .lede{margin-inline:auto}
.sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.nowrap{white-space:nowrap}
@media (prefers-reduced-transparency:reduce){
  .hdr.is-stuck{background:#fff;backdrop-filter:none;-webkit-backdrop-filter:none}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important;
  }
}


/* ==========================================================================
   Components added in the second pass
   ========================================================================== */

/* The board: one image, nothing over it.

   It used to be a rendered film behind a play overlay, which needed a bespoke
   box (absolute-positioned poster and video, a scrim, a ring) and a script to
   drive it. With the overlay gone there is nothing left for any of that to do,
   so the board uses the shared .frame like every other picture on the site.
   The source composition is still in videos/hero-director/ if the film is ever
   wanted back. */

/* the hero that holds it: copy left, film right, both centred */
.hero-dir{grid-template-columns:minmax(0,1fr) minmax(0,1.03fr);align-items:center;margin-top:clamp(8px,1.4vw,18px)}
.hero-dir .hero-copy{max-width:none;gap:18px;padding-top:0}
.hero-dir .hero-h1{font-size:clamp(2.05rem,3.85vw,3.3rem);max-width:19ch}
.hero-dir .lede{max-width:53ch}
.hero-dir .tiny{max-width:46ch}
@media (max-width:1023px){
  .hero-dir{grid-template-columns:1fr}
  .hero-dir .hero-h1{max-width:24ch}
}
.words .ac{color:var(--accent-fg)}

/* ---------- the output gallery: real clips, loaded only when in view ---------- */
/* The strip is the proof on this page, so the clips are the size of the claim.
   --speed is the time for one pass of the whole track, so it scales with the
   card width: keep it in step or wider cards simply travel faster. */
.vgal{--speed:62s}
.vcard{
  position:relative;flex:0 0 auto;width:clamp(196px,21vw,304px);aspect-ratio:9/16;
  border-radius:18px;overflow:hidden;border:1px solid var(--line);background:#0C0F1C;
  box-shadow:0 24px 60px -34px rgba(11,16,32,.55);
}
.vcard>img,.vcard>video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.vcard>video{opacity:0;transition:opacity .5s ease}
.vcard>video.is-playing{opacity:1}
.vcard::after{content:"";position:absolute;inset:0;border-radius:inherit;box-shadow:inset 0 0 0 1px rgba(255,255,255,.07);pointer-events:none}
/* The heading and its note sit on one line and share the width, so the block
   above the strip costs a couple of rows instead of six. Each measure is set on
   the element whose own font it is counted in. */
.vgal-head .eyebrow{margin-bottom:10px}
.vgal-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 30px}
.vgal-line h2{max-width:20ch;flex:0 1 auto}
.vgal-line .small{flex:1 1 300px;max-width:56ch;color:var(--fg-3);margin:0}
.vgal-sec .marq{padding-block:clamp(22px,2.6vw,38px)}
/* the track gap grows with the cards, or big clips read as one long ribbon */
.vgal .marq-track{gap:clamp(12px,1.5vw,22px)}

/* ---------- a scrolling row of creatives (Platform) ----------
   Sized by height rather than width: each card keeps whatever aspect-ratio its
   frame class sets and the width follows from it, so the band stays one height
   even if a square set is replaced by a vertical one. Slower than the video
   gallery because these are stills -- there is nothing playing to hold the eye,
   so the row itself has to be readable as it passes. */
.crow{--speed:88s}
.crow .marq-track{gap:clamp(10px,1.2vw,18px);align-items:center}
.crow-c{flex:0 0 auto;height:clamp(168px,19vw,248px);width:auto}
.crow-c>img{position:absolute;inset:0}

/* ---------- the standard page header ------------------------------------
   Every page under the homepage opens with this: path, title, description, and
   nothing beside it. Sized to stay under half the viewport on a laptop, which
   is the whole point -- the content should start above the fold, not after a
   full screen of chrome. */
.phead{
  padding-block:clamp(32px,5vh,70px) clamp(30px,4.5vh,62px);
  background:var(--bg);
  border-bottom:1px solid rgba(255,255,255,.07);
}
.ph-in{max-width:none}
/* The picture beside the title. Two columns from 1024px, the picture under the
   copy below that, and the copy keeps its own measure so the line length does
   not change when the picture appears. */
.phead-2 .wrap{display:grid;gap:26px}
.ph-a{min-width:0;margin:0}
.ph-a>.frame,.ph-a>img{width:100%}
.ph-a>img{display:block;height:auto;border-radius:var(--r-card);border:1px solid rgba(255,255,255,.12)}
.ph-a .cap{color:rgba(255,255,255,.42)}
/* A header picture with no frame around it, at every width. The Product pages'
   pictures are workspaces, dashboards and diagrams that carry their own dark
   ground onto a dark band, so a border was drawing a line around a picture that
   did not need one -- and a 16px radius was clipping its corners. With the
   frame gone and each picture on its own ratio (frame-auto) nothing is cropped
   and the whole slot is the picture: 778x395 on Covely Ads at 1440 instead of
   778x437 with 9.7% of the width cut off, and 778x438 on MCP instead of a
   1240x698 diagram matted into a 16:9 box.

   Platform already had this above 1024px, from the .phead-hero block further
   down, and now has it on a phone too. */
.ph-a .ph-bare{border:0;border-radius:0;box-shadow:none;background:none}
@media (min-width:1024px){
  .phead-2 .wrap{grid-template-columns:minmax(0,1fr) minmax(0,clamp(400px,40vw,540px));gap:clamp(28px,4vw,56px);align-items:center}
  .phead-2 .ph-h1{max-width:20ch}
  .phead-2 .lede{max-width:48ch}
}
/* The Product pages' header: a longer band and a bigger picture slot.

   Those pages put a workspace, a dashboard or a diagram beside the title, and
   at the standard 540px the labels inside them were too small to read — which
   defeats the point of showing an interface at all. The slot goes to 700px, and
   because the pictures keep their ratio the band grows with them; a little more
   padding on top of that stops the picture touching the band's edges.

   The copy column still holds a comfortable measure at 1440 (about 620px, and
   the title is capped at 20ch), so the headline does not start wrapping every
   two words to pay for the picture. Below 1240px the slot steps back down and
   below 1024 the whole thing stacks, exactly as before. */
@media (min-width:1240px){
  .phead-lg .wrap{grid-template-columns:minmax(0,1fr) minmax(0,clamp(560px,50vw,700px))}
  .phead-lg{padding-block:clamp(38px,5.6vh,80px) clamp(36px,5vh,72px)}
  .phead-lg .ph-h1{max-width:19ch}
  .phead-lg .lede{max-width:46ch}
}

/* ---- a subpage header at the homepage's type ----

   On Platform only, as a test. The band has been a full screen since the hero
   change and was still carrying header-sized type in it, which reads as a
   header stretched rather than as a hero.

   The numbers are the homepage hero's own, not new ones: h1 on
   clamp(2.05rem,3.85vw,3.3rem) against the header's clamp(2rem,3.7vw,3.05rem),
   and the lede on the site's .lede clamp -- up to 1.2rem -- instead of the
   header's flat 1.0625rem. The measures come with them, 19ch and 53ch, or the
   larger type would run the title to five lines.

   .ph-meta becomes a stack rather than a row so the buttons and the line under
   them sit like the homepage's. */
/* .phead.phead-hero, not .phead-hero: the flat 1.0625rem on .phead .lede sits
   further down this file and would otherwise win on source order. The
   line-height is the homepage's 1.04, from .d0, not the header's 1.06. */
/* The picture on this page is square, not 16:9, and it has no frame: the board
   carries its own dark margin onto a dark band, so a border was drawing a line
   around a picture that did not need one.

   With the frame and the caption gone it takes the room they were using. What
   bounds it is the screen rather than a number: a square picture sized by its
   width would otherwise set a band taller than the one screen this hero is, so
   the ceiling is the viewport less the header less this band's own padding. At
   1440x900 that is 724px, against 634 with the frame on it. On a taller screen
   the 760px column takes over as the limit. */
@media (min-width:1240px){
  .phead.phead-hero .wrap{grid-template-columns:minmax(0,1fr) minmax(0,clamp(460px,52vw,760px))}
}
@media (min-width:1024px){
  .phead.phead-hero .ph-a{
    max-width:min(100%,calc(100vh - var(--nav-h) - 104px));
    max-width:min(100%,calc(100svh - var(--nav-h) - 104px));
    /* width:100% and not just the auto margin. An auto margin on a grid item
       stops it stretching and sizes it to its content instead -- and its content
       is a frame at width:100%, which resolves to the poster's own intrinsic
       width. Platform never showed it because its board is 1254px, wider than
       the column, so the max-width bit first. Product Content's poster is 429px
       and the film in this header drew at 429 instead of 677. */
    width:100%;
    margin-left:auto;
  }
  .phead.phead-hero .ph-a .frame{border:0;border-radius:0;box-shadow:none;background:none}
}
.phead.phead-hero .ph-h1{font-size:clamp(2.05rem,3.85vw,3.3rem);line-height:1.04;max-width:19ch}
.phead.phead-hero .lede{font-size:clamp(1.0625rem,1.45vw,1.2rem);max-width:53ch}
.phead-hero .ph-meta{margin-top:clamp(20px,2.2vw,28px);display:grid;gap:14px;justify-items:start}
.phead-hero .tiny{max-width:46ch}
@media (max-width:639px){.phead-hero .ph-h1{max-width:none}}

/* ---- the homepage's colour ground, in a page header ----
   .aur is absolutely positioned at inset:0, so the section has to be its
   containing block and has to clip it -- the same two lines .hero carries. And
   the header's own content has to be positioned to paint over it: a positioned
   element with z-index:0 draws above a static sibling whatever the source
   order, which is why .hero>* exists and why this needs its own copy. */
.phead-aur{position:relative;overflow:hidden}
/* :not(.aur) is load-bearing. .aur is a direct child, so a bare .phead-aur>*
   sets position:relative on it too -- same specificity as .aur's own
   position:absolute and further down the file, so it wins, the layer collapses
   to a zero-height block in flow and the colour disappears entirely. .hero>*
   gets away with the bare selector only because it sits above .aur in this
   file. */
.phead-aur>*:not(.aur):not(.gen){position:relative;z-index:1}
.phead-aur>.aur,.phead-aur>.gen{z-index:0}

/* ---- the generation field, PARKED ----
   Nothing renders .gen since 10 September 2026: the AI Avatar header went back
   to the homepage's aurora. Kept because it was parked, not rejected -- see
   genField() in lib/kit.mjs. This block is what it costs to keep: delete both
   and the effect is gone for good.

   A light sweeps the band once, and where it passes, finished assets exist.
   They hold, dissolve, and the next pass makes them again in another colour.
   Nothing travels across the band, so this is not a scroll: it is the
   product's loop -- brief in, assets out, again -- as one gesture. The frames
   carry the real output ratios, which on this page is also what a twin ships
   as. Geometry is in lib/kit.mjs; see the note there.

   The whole field is masked away from the copy, so nothing it does -- the
   beam, the wash, the pointer light -- can ever reach the text. That is the
   one property everything else here is allowed to be loud inside of. */
.gen{
  --dur:11s;
  --px:76%;--py:50%;--pon:0;
  position:absolute;inset:0;overflow:hidden;pointer-events:none;
}
@media (min-width:900px){
  .gen{
    /* Clear of the copy's BLOCK, not of the longest line it happens to have
       today. The lede's block ends at 60.5% of the band; the field is fully
       transparent to 59 and only reaches full at 72. */
    -webkit-mask-image:linear-gradient(90deg,transparent 0,transparent 59%,#000 72%,#000 100%);
    mask-image:linear-gradient(90deg,transparent 0,transparent 59%,#000 72%,#000 100%);
  }
}
/* the ground the set is generated on: a slow colour field, so the band is
   never flat even between passes */
.gen-wash{
  position:absolute;inset:-20%;
  background:
    radial-gradient(38% 52% at 72% 30%,rgba(124,77,255,.34),transparent 70%),
    radial-gradient(34% 48% at 88% 72%,rgba(255,46,126,.30),transparent 70%),
    radial-gradient(30% 44% at 58% 66%,rgba(56,110,255,.26),transparent 72%);
  filter:blur(24px);
}
/* one generated asset */
.gen-c{
  position:absolute;display:block;
  left:calc(var(--x) * 1%);top:calc(var(--y) * 1%);
  width:calc(var(--w) * 1%);aspect-ratio:var(--ar);
  border-radius:clamp(7px,.8vw,13px);
  border:1px solid rgba(255,255,255,.22);
  background:linear-gradient(145deg,var(--c1),var(--c2));
  box-shadow:0 0 0 1px rgba(0,0,0,.28),0 18px 44px -20px var(--c1);
  /* Resting state is the RESOLVED one and the keyframes start from nothing,
     not the other way round. With opacity:0 here the reader who asked for
     reduced motion -- where none of these animations is applied -- got an
     empty band. Measured as exactly that before this line was turned round. */
  opacity:.96;
  transition:filter .45s var(--ease-out);
}
/* four hues, so the set is a palette rather than one colour repeated */
.gen-h0{--c1:rgba(255,46,126,.92);--c2:rgba(255,138,61,.55)}
.gen-h1{--c1:rgba(124,77,255,.92);--c2:rgba(255,46,126,.5)}
.gen-h2{--c1:rgba(56,110,255,.9);--c2:rgba(0,194,199,.5)}
.gen-h3{--c1:rgba(255,110,170,.9);--c2:rgba(124,77,255,.5)}
/* the printhead */
.gen-beam{
  position:absolute;top:-12%;bottom:-12%;left:0;width:clamp(80px,8vw,150px);
  background:linear-gradient(90deg,transparent,rgba(124,77,255,.30) 30%,rgba(255,46,126,.62) 60%,rgba(255,190,220,.75) 76%,transparent);
  filter:blur(11px);mix-blend-mode:screen;opacity:0;
}
/* the light the pointer carries */
.gen-spot{
  position:absolute;left:0;top:0;width:min(38vw,520px);aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle,rgba(255,160,205,.5),rgba(124,77,255,.2) 42%,transparent 68%);
  filter:blur(24px);mix-blend-mode:screen;
  transform:translate3d(calc(var(--px) - 50%),calc(var(--py) - 50%),0);
  opacity:0;
}

/* Below 900 the copy is the full width, so there is nowhere for the set to be
   that is not behind it -- and at 4% of a 390px band a frame is 16px, which
   reads as confetti rather than as an asset. The frames and the beam come off
   and the colour ground stays: the band is still not flat, and the copy is on
   clean dark. */
/* After the base rules, not before them: .gen-c sets display:block further up
   the file, and at equal specificity the later plain rule beats an earlier one
   in a media query. Measured as 13 cells still displayed at 390 with this
   block sitting above them. */
@media (max-width:899px){
  .gen-c,.gen-beam,.gen-spot{display:none}
  .gen-wash{opacity:.6}
}

@media (prefers-reduced-motion:no-preference){
  /* Each cell resolves as the beam reaches it -- its delay IS its place in the
     sweep -- holds while the set is complete, then dissolves. No cell travels;
     what moves is the light, once per cycle. */
  .gen-c{animation:gen-make var(--dur) var(--ease-out) infinite;animation-delay:calc(var(--i) * var(--dur) / 46)}
  .gen-beam{animation:gen-beam var(--dur) var(--ease-in-out) infinite}
  .gen-wash{animation:gen-wash 21s var(--ease-in-out) infinite alternate}
  /* the palette walks across passes, so no two generations are the same set */
  .gen{animation:gen-hue calc(var(--dur) * 3) linear infinite}
  .gen-spot{transition:transform .45s cubic-bezier(.22,1,.36,1),opacity .45s ease}
  @media (hover:hover){
    /* The pointer lights the set and carries a light with it. What it does NOT
       do is change animation-duration: the frames' delays are the beam's
       positions, so shortening one without the other pulls the light off the
       frame it is supposed to be printing -- and changing a duration
       mid-cycle reinterprets the current time against the new one, which
       shows as a stutter. Brightness costs nothing and breaks nothing. */
    .phead:hover .gen-spot{opacity:calc(var(--pon) * 1)}
    .phead:hover .gen-c{filter:saturate(1.3) brightness(1.18)}
    .phead:hover .gen-wash{opacity:1.15}
  }
}
/* nothing here travels: a frame is not there, then it is */
@keyframes gen-make{
  0%    {opacity:0;transform:translateY(14px) scale(.86)}
  9%    {opacity:1;transform:translateY(0) scale(1.03)}
  14%   {opacity:.96;transform:translateY(0) scale(1)}
  52%   {opacity:.96;transform:translateY(0) scale(1)}
  66%   {opacity:0;transform:translateY(-8px) scale(1.05)}
  100%  {opacity:0;transform:translateY(14px) scale(.86)}
}
/* The beam has to BE where the frame appears, or the two are just two things
   happening. The set lights from --i * dur/46 plus the 9% each cell takes to
   resolve: the first at 9% of the cycle and the last at 35%. So the beam is at
   56vw at 9% and 100vw at 35%, which is where the first and the last frame
   are. Travel is in vw rather than in multiples of its own width because the
   beam's width is clamped and the frames' positions are not -- tie it to
   itself and the two drift apart at every viewport but the one it was tuned
   at. The field is full-bleed, so vw is the field. */
@keyframes gen-beam{
  0%    {opacity:0;transform:translateX(41vw)}
  6%    {opacity:.9}
  9%    {opacity:1;transform:translateX(56vw)}
  35%   {opacity:1;transform:translateX(100vw)}
  44%   {opacity:0;transform:translateX(115vw)}
  100%  {opacity:0;transform:translateX(41vw)}
}
@keyframes gen-wash{
  from{transform:scale(1) translate(0,0)}
  to{transform:scale(1.12) translate(-3%,2%)}
}
@keyframes gen-hue{
  0%{filter:hue-rotate(0)}
  50%{filter:hue-rotate(-42deg)}
  100%{filter:hue-rotate(0)}
}

/* Product Content's header had its own column split, phead-pc, so the film
   and then the board beside the title had the room they needed. Both are off
   the header now and so is the split. */

/* ---- how it works: the square film beside the steps ----
   The six use-case films are 1080x1080. Beside the numbered list they draw 414
   to 497px, which is the right weight, but the split collapses to one column at
   939 and the film would then take the whole measure: 859px square on a tablet,
   a screen and a half of video before the first step, and the poster upscaled
   over its poster. Capped instead, and centred, so the stacked order reads
   heading, film, steps without any of the three swallowing the viewport. The cap
   never binds while the split is two columns -- the narrower track is 414 to
   497px at every width above 939.

   500, not 560: the smallest stand-in poster is 480px on its short side, so a
   500 cap draws it at 1.04x and nothing on any of the six is ever blown up. */
/* width:100%, not just max-width: auto inline margins take a grid item out of
   the default stretch, so the figure sized to its content instead of its track
   -- and its content is the poster's intrinsic width. The one portrait
   stand-in, 480px wide, drew the film at 482 where the other five drew 493. */
.hiwf{width:100%;max-width:min(100%,500px);margin-inline:auto}

/* ---- one thing in, several out ----
   The source, an arrow, and the things made from it, on one line under the
   header. Product Content had it first and every use case has it now; the count
   is not always four, so the row reads --n off the band.

   On Product Content each file is already a white card with its own shadow, so
   nothing here adds a frame, a border or a radius -- the card in the picture is
   the card you see, which is also why the band is white and not the grey one.
   Everywhere else the pictures are stills and .frame does that job, with a
   label in type underneath.

   The copy column is a fixed width rather than a fraction of the measure, and
   the two-column line only runs from 1320px up. Product Content's labels are
   drawn into the files at 430 to 456px wide, so a card's scale is its
   legibility: at 1440 an output card is 166px, which is the comp's own 159, and
   by 1240 the same line would have squeezed it to 140 and the labels under 9px.
   Below 1320 the copy goes on top and the row takes the full measure, which
   draws the cards at 230 and up -- bigger than the comp, and the right trade
   when the line will not fit at the size it was drawn for.

   Flex rather than a grid, because a count is not always even: three outputs at
   two per row leave an orphan, and justify-content centres it instead of
   parking it in column one beside a hole.

   Below 600 the outputs break two by two under the source, with the arrow
   turned a quarter so it still points at them. */
.split-pc{grid-template-columns:minmax(0,clamp(300px,26vw,390px)) minmax(0,1fr);align-items:center}
.varn{
  --vs:clamp(120px,13vw,190px);--va:clamp(20px,2vw,30px);--vp:clamp(6px,.9vw,14px);
  display:grid;grid-template-columns:auto auto minmax(0,1fr);align-items:center;gap:var(--vp);
}
/* No source picture -- audio-voice, where a photograph of a script would be a
   picture of the wrong thing, and UGC, where the row is five clips. The row is
   the outputs and nothing else. */
.varn-nosrc{grid-template-columns:minmax(0,1fr)}
/* A SHORT row with no source holds the width it would have had beside one, so
   the band is the same size on every page: audio's three 9:16 posters given the
   whole measure drew 702px tall at 1240, twice the height of the same posters
   next door. Four of them held draws 163px at 1440, which is exactly what a
   source-bearing row draws, so the threshold is four.
   A row of five needs no holding -- it draws 178px at 1440 on its own -- and
   held it would have been squeezed to 129.

   Between 600 and 899 every count needs it. The band is still a three-track
   grid there, so a source-bearing row gives up 159px of its measure to the
   card and the arrow, and any row that does not draw 2-up at 341 against its
   siblings' 264 -- five clips at 606px tall each, three rows of them. */
@media (min-width:600px) and (max-width:899px){
  .varn-nosrc .varn-o{max-width:calc(100% - var(--vs) - var(--va) - 2*var(--vp));margin-inline:auto}
  /* width, not max-width: see the note on the ceiling below. */
  .varn-cms .varn-o{width:calc(100% - var(--vs) - var(--va) - 2*var(--vp));margin-inline:auto}
}
@media (min-width:900px){
  .varn-hold .varn-o{max-width:calc(100% - var(--vs) - var(--va) - 2*var(--vp));margin-inline:auto}
}
.varn-p{margin:0;min-width:0}
/* Direct child only: a framed picture is .varn-p > .frame > img, and height:auto
   there would undo the frame's ratio. */
.varn-p>img{display:block;width:100%;height:auto}
.varn-src{width:var(--vs)}
.varn-a{display:grid;place-items:center;color:var(--accent-fg)}
.varn-a .ico{width:var(--va);height:var(--va)}
.varn-o{--vg:clamp(4px,.6vw,10px);display:flex;flex-wrap:wrap;justify-content:center;gap:var(--vg);min-width:0}
.varn-o>*{flex:0 1 calc((100% - (var(--n,4) - 1)*var(--vg))/var(--n,4));min-width:0}
.varn-l{margin-top:8px;font-size:.75rem;line-height:1.3;color:var(--fg-3);text-align:center}
/* Cards that do not share a shape. Flex with a percentage basis cannot give
   them proportional widths, so this row is a grid whose tracks come from the
   band as --cols -- one fr per card, equal to its aspect -- which is what makes
   a 16:9 card and two 9:16 cards come out the same height instead of 169px
   against 535. Below 900 the landscape card takes the full row and the two
   uprights share the line under it, which is the only arrangement that does not
   either squeeze the wide one or run the tall ones off the screen. */
.varn-mix .varn-o{display:grid;grid-template-columns:var(--cols);align-items:start}
/* A band the CRM filled draws the same on all six use cases.

   The six bands the pages ship with are not alike -- five clips at 9:16 on UGC,
   five pictures at 4:5 on Ads and Social, one landscape and two uprights on
   Film, three on Audio, and on Product Content a source photograph, an arrow
   and four outputs. Inherit that and the same three videos loaded in the panel
   come out a different size and shape on each page, which is the one thing a
   band the panel drives must not do.

   So when the panel fills it, the band takes one shape everywhere: the outputs
   get the whole measure, the source card and its arrow come off, and the copy
   beside it keeps the page's own column. What is left is UGC's band, on all
   six.

   The section still gives it exactly what it gives a hand-written row -- same
   copy column, same gap, same air above and below. */
.varn-cms{grid-template-columns:minmax(0,1fr)}
.varn-cms .varn-src,.varn-cms .varn-a{display:none}
/* And a card is never bigger than the five on UGC. --cap is the height those
   five draw in this measure, and --asum the sum of the cards' aspects, so this
   reads "no taller than a UGC card".

   width, not max-width. A grid item with auto inline margins is sized to its
   content rather than stretched to its track, and max-width does not change
   that -- so a row whose pictures report no intrinsic size collapses onto
   them. Which is exactly what happened on Film & Storytelling: three rows
   published with the .mp4 itself in the poster field, three <img> that can
   never decode, naturalWidth 0, and a row 194px wide carrying 59px cards. A
   definite width is not content-sized, so the row holds its measure whatever
   the pictures do, and the auto margins then centre it.

   The ceiling itself: at five 9:16 the two sides are equal and
   the row fills the measure exactly, at six or more the measure is the tighter
   limit and they shrink to stay on the line, and at two or three the cap is,
   so they keep the same 178px width with the row centred instead of stretching
   to 560px tall. One rule, and it holds for the proportional grid a mixed set
   becomes as well as for the flex row.

   One row, from 900 up, where the copy moves beside the band and the row has
   the measure to itself: a card dropped onto a second line is the one thing
   this band must not do up there. Below 900 it wraps like every written row --
   five cards held to one line at 768 came out 100px wide against the 264 UGC's
   own five draw across three lines, and a video that small is not a video. */
@media (min-width:900px){
  .varn-cms .varn-o{
    --cap:calc((100% - 4 * var(--vg)) / 5 * 16 / 9);
    width:min(100%, calc(var(--cap) * var(--asum,2.8125) + (var(--n,5) - 1) * var(--vg)));
    margin-inline:auto;
    flex-wrap:nowrap;
  }
}

/* A picture the page draws no chrome around, for a board whose artwork already
   carries its own edge. Block, so it does not sit on a text baseline, and
   full-width with the height following, since it runs at its own ratio. */
.pic-bare{display:block;width:100%;height:auto;border-radius:0}
/* The same for a picture that still wants a ratio: a frame with no frame. For
   photographs and boards that carry their own edge, where the panel, the border
   and the shadow draw a card around something that is already one. */
.fr-bare{background:none;border:0;box-shadow:none;border-radius:0}

/* ---- the loop: brief in, ads out, round again ----------------------------

   Six steps on a circle, because step six writes step one, beside the copy
   rather than under it. The comp this is built from is in the note on `loop`
   in pages/services.mjs, along with what the smaller size costs.

   Geometry, computed and then measured rather than nudged: the ring is a square
   capped at 790px, the template places each node from its own angle, and a
   radius of 36.5% puts a 230px card fully inside the box with the nearest pair
   clearing by 21px. The centre is 240x148 and sits in the diamond between the
   four side cards with 10px to spare, which is why it holds the team and its
   two lines and not the five pills as well -- those are chips beside the ring.

   The comet is three arcs sharing a head rather than a gradient, because a
   linear gradient does not follow a curve: 92, 48 and 17 units of a
   459-unit circumference at rising opacity, plus a dot at the head. */
.cyc{position:relative;width:100%;max-width:900px;margin-inline:auto;aspect-ratio:1}
.cyc-ring{position:absolute;inset:0;width:100%;height:100%}
.cyc-ring circle{fill:none}
.cyc-track{stroke:var(--line-2);stroke-width:1}
.cyc-tail,.cyc-mid,.cyc-head{stroke:var(--accent);stroke-linecap:round;transform-origin:100px 100px}
/* Each arc starts at the top and runs BACK from it, so all three share a head
   at 12 o'clock: a circle's dash begins at 3 o'clock, so -90 puts it at the
   top and the extra rotation is the arc's own length. */
.cyc-tail{stroke-width:2;opacity:.2;stroke-dasharray:91.7 458.7;transform:rotate(-162deg)}
.cyc-mid{stroke-width:2.4;opacity:.48;stroke-dasharray:48.4 458.7;transform:rotate(-128deg)}
.cyc-head{stroke-width:3;stroke-dasharray:16.6 458.7;transform:rotate(-103deg)}
.cyc-dot{fill:var(--accent);stroke:none}
.cyc-spin{transform-origin:100px 100px}

/* The middle: the team, and what does not change while the ring turns. */
.cyc-c{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  display:grid;justify-items:center;gap:2px;width:min(240px,32%);text-align:center;
}
.cyc-team{width:100%;border:0;border-radius:0;background:none;box-shadow:none;margin-bottom:6px}
.cyc-cl{font-family:'Space Grotesk',sans-serif;font-size:1.0625rem;font-weight:600;letter-spacing:-.015em;color:var(--fg)}
.cyc-cs{font-size:.8125rem;color:var(--fg-3)}

.cyc-l{list-style:none;margin:0;padding:0}
/* The comp's card: copy on the left, the picture on the right, the badge over
   the corner. A gradient ground rather than flat white, so the crops -- which
   carry their own very light lavender -- sit on it without an edge showing. */
.cyc-n{
  position:absolute;left:var(--x);top:var(--y);transform:translate(-50%,-50%);
  width:min(262px,29.5%);min-height:128px;display:grid;grid-template-columns:minmax(0,1fr) 96px;
  align-items:center;gap:11px;padding:14px 14px 14px 16px;
  border-radius:var(--r-card);
  background:linear-gradient(150deg,#fff 40%,#fdf1f7);
  border:1px solid var(--line);box-shadow:var(--shadow);
  transition:border-color .25s var(--ease),box-shadow .25s var(--ease),transform .25s var(--ease);
}
.cyc-x{display:grid;gap:3px;min-width:0}
.cyc-b{
  position:absolute;top:-14px;left:-14px;width:32px;height:32px;display:grid;place-items:center;
  border-radius:50%;background:var(--accent);color:#fff;
  box-shadow:0 4px 14px rgba(255,46,126,.35);
  transition:transform .25s var(--ease),box-shadow .25s var(--ease);
}
.cyc-b .ico{width:16px;height:16px}
.cyc-p{
  border:0;border-radius:10px;background:none;box-shadow:none;overflow:hidden;
}
.cyc-p img{transition:transform .35s var(--ease)}
.cyc-num{font-family:'Space Grotesk',sans-serif;font-size:.625rem;font-weight:600;letter-spacing:.14em;color:var(--accent-fg)}
.cyc-t{font-family:'Space Grotesk',sans-serif;font-size:.875rem;font-weight:600;letter-spacing:-.01em;color:var(--fg)}
.cyc-d{font-size:.75rem;line-height:1.35;color:var(--fg-2)}
.cyc-w{
  display:inline-flex;align-items:center;gap:4px;margin-top:3px;padding:3px 8px;
  border-radius:999px;background:var(--accent-dim);
  font-size:.625rem;letter-spacing:.02em;color:var(--accent-fg);width:max-content;
}
.cyc-w .ico{width:11px;height:11px}

/* The lit state is painted by an overlay ring, not by the card's own border.
   A running animation outranks every normal author rule for the properties it
   touches, whatever the specificity -- so animating the card's border-color
   here would have made the hover border below dead code (measured: a hovered
   card kept rgba(11,16,32,.1) while the cycle ran). The overlay owns
   border-color and box-shadow for the cycle; the card keeps its own for hover,
   and the two can be true at once. */
.cyc-n::after{
  content:"";position:absolute;inset:-1px;border-radius:inherit;pointer-events:none;
  border:1px solid transparent;
}

/* More on hover, at request: the card lifts and takes the accent, its picture
   grows inside its own frame, and the badge steps up. The ring's cycle keeps
   running underneath -- pausing it would make the graphic look broken to
   somebody who is only passing through. */
.cyc-n:hover,.cyc-n:focus-within{
  border-color:var(--accent);
  box-shadow:0 0 0 4px var(--accent-dim),0 18px 40px -18px rgba(255,46,126,.45),var(--shadow-lg);
  transform:translate(-50%,-50%) translateY(-4px);
  z-index:2;
}
/* .frame.cyc-p, not .cyc-p: the reveal's own .rv.is-in .frame img{transform:none}
   sits 1600 lines further down at the same weight and was winning on order,
   so the picture never moved. One extra class settles it whatever the order. */
.cyc-n:hover .frame.cyc-p img,.cyc-n:focus-within .frame.cyc-p img{transform:scale(1.08)}
.cyc-n:hover .cyc-b,.cyc-n:focus-within .cyc-b{
  transform:scale(1.12);box-shadow:0 6px 20px rgba(255,46,126,.5);
}

@media (prefers-reduced-motion:no-preference){
  .cyc-spin{animation:cyc-spin 18s linear infinite}
  .cyc-n::after{animation:cyc-node 18s linear infinite;animation-delay:calc(var(--i) * 3s)}
}
/* Reduced motion keeps the colour and the shadow on hover and drops the two
   things that move, so the affordance survives without the animation. */
@media (prefers-reduced-motion:reduce){
  .cyc-n:hover,.cyc-n:focus-within{transform:translate(-50%,-50%)}
  .cyc-n:hover .frame.cyc-p img,.cyc-n:focus-within .frame.cyc-p img{transform:none}
  .cyc-n:hover .cyc-b,.cyc-n:focus-within .cyc-b{transform:none}
}
@keyframes cyc-spin{to{transform:rotate(360deg)}}
/* On while the head is on this node, then handed to the next one.

   The hand-off is why the numbers are 13 and 16.6 rather than something
   rounder: the next node lights at a sixth of the turn, 16.667%, so the fade
   has to be FINISHED by then. At 14 and 20 it was not -- the outgoing card sat
   at 0.435 accent for 1.08s after the incoming one was fully lit, and two
   adjacent cards glowing at once says "here and here" instead of "here". The
   fade now ends a tenth of a percent before the baton passes, so exactly one
   card is lit at any moment and the light never blinks out between steps.

   It runs on .cyc-n::after, so nothing here competes with the hover rule. */
@keyframes cyc-node{
  0%,13%{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-dim)}
  16.6%,100%{border-color:transparent;box-shadow:0 0 0 0 transparent}
}

/* Below 1100 the circle and a copy column cannot share the measure: the ring
   drops under 620px and six 230px cards collide. The copy goes on top and the
   ring takes the full measure, which is also where the illustrations are
   biggest -- so the widest phone is not the worst place to read this. */
/* And below 900 the circle is the wrong shape for the space altogether. The
   same list runs vertically, with the rail down the left standing in for the
   ring and the same lit step travelling down it. */
@media (max-width:899px){
  .cyc{max-width:560px;aspect-ratio:auto}
  .cyc-ring,.cyc-c{display:none}
  .cyc-l{display:grid;gap:14px;border-left:1px solid var(--line-2);padding-left:24px}
  /* relative, not static: the badge is absolute inside the card, and a static
     card would hand all six badges to .cyc and stack them in one corner. */
  .cyc-n{position:relative;left:auto;top:auto;transform:none;width:auto;grid-template-columns:minmax(0,1fr) 104px}
  .cyc-n:hover,.cyc-n:focus-within{transform:translateY(-3px)}
  /* the badge stays inside the card's own left edge here: hung out over the
     rail it clipped against the page gutter at 390. */
  .cyc-b{left:-14px;top:14px}
}
@media (max-width:479px){
  .cyc-n{grid-template-columns:minmax(0,1fr) 78px}
}

/* Two columns that start on the same line. .split centres its tracks, which is
   right when they are close in height and wrong when one is a three-line list
   and the other a stack of pictures twice as tall -- the list then floats in
   the middle of the band with nothing to align to. */
.split-top{align-items:start}
@media (max-width:899px){
  .varn-mix .varn-o{grid-template-columns:repeat(2,minmax(0,1fr))}
  .varn-mix .varn-o>:first-child{grid-column:1/-1}
}
@media (max-width:1319px){
  .split-pc{grid-template-columns:1fr;gap:clamp(26px,3vw,40px)}
}
/* Four across a tablet's measure is 131px a card -- 30% of the file, and
   Product Content's label drawn into it at 7px. Two by two instead, which
   doubles them. Redeclaring --n on the row wins over the inline value it
   inherits from the band. */
@media (max-width:899px){
  .varn-o{--n:2;--vg:clamp(8px,1.2vw,14px)}
}
@media (max-width:599px){
  .varn{grid-template-columns:1fr;justify-items:center;gap:10px}
  .varn-src{width:min(62%,220px)}
  .varn-a{transform:rotate(90deg)}
  .varn-o{--n:2;--vg:8px;width:100%}
  .varn-l{font-size:.6875rem}
}

/* ---- one screen, like the homepage ----

   Every page under Product and Use Cases opens on a full screen now, at
   request. Same mechanism as .hero-full above and for the same reasons:
   min-height rather than height so copy that outgrows the viewport pushes the
   band taller instead of being cut off, svh so the mobile URL bar cannot make
   the first screen jump, and align-content:center so the band grows around the
   title rather than pushing it down.

   Off below 1024, exactly as .hero-full is: once the picture sits under the copy
   the two together already run past a screen, and holding a floor as well would
   put a screenful of empty under a stacked hero.

   The picture slot goes to 820px on the widest screens. A band this tall with a
   700px picture in it is mostly air, and every Product header picture is at
   least 1240px wide, so none of them is being stretched to fill it. */
@media (min-width:1024px){
  .phead-lg{
    min-height:calc(100vh - var(--nav-h));min-height:calc(100svh - var(--nav-h));
    display:grid;align-content:center;
  }
}
@media (min-width:1240px){
  .phead-lg .wrap{grid-template-columns:minmax(0,1fr) minmax(0,clamp(560px,54vw,820px))}
}

/* Vertical media: the slot narrows so the picture keeps its own shape without
   making the band taller than the copy beside it. */
.ph-a-v{max-width:400px;margin-inline:auto}
@media (max-width:1023px){
  .ph-a{max-width:540px}
  .ph-a-v{max-width:340px;margin-inline:0}
}
/* A set of pictures rather than one: two across, and the caption under both.
   Capped tighter than a single picture -- four cells at the full aside width
   made a header eight hundred pixels tall. */
.ph-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;max-width:430px}
.ph-grid .cap{grid-column:1/-1}
/* And where no honest picture exists, the facts, set as a panel. */
.ph-pane{
  padding:20px 22px;border-radius:var(--r-card);
  border:1px solid rgba(255,255,255,.13);background:rgba(255,255,255,.04);
}
.ph-pane h2,.ph-pane p{margin:0}
.ph-pane .pht{font-size:.6875rem;text-transform:uppercase;letter-spacing:.09em;color:rgba(255,255,255,.42)}
.ph-pane .phr{display:flex;justify-content:space-between;gap:14px;font-size:.875rem;color:rgba(255,255,255,.66);padding:7px 0}
.ph-pane .phr+.phr{border-top:1px solid rgba(255,255,255,.08)}
.ph-pane .phr b{color:#fff;font-weight:600;text-align:right;white-space:nowrap}
.ph-pane .phf{margin-top:12px;padding-top:11px;border-top:1px solid rgba(255,255,255,.1);font-size:.75rem;color:rgba(255,255,255,.44);line-height:1.5}
.ph-pane a{color:#fff;text-decoration:underline;text-decoration-color:var(--accent);text-underline-offset:3px}
/* A long document has a long list of sections. It scrolls inside the panel
   rather than setting the height of the whole band; tabbing through the links
   scrolls it, so there is nothing here a keyboard cannot reach. */
.ph-pane .phl{
  display:grid;gap:2px;margin-top:10px;max-height:290px;overflow-y:auto;
  /* the last row fades, so a list of nineteen does not look like a list of seven */
  mask-image:linear-gradient(#000 calc(100% - 26px),transparent);
}
.ph-pane .phl a{
  display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:9px 0;font-size:.875rem;color:rgba(255,255,255,.78);text-decoration:none;
}
.ph-pane .phl a+a{border-top:1px solid rgba(255,255,255,.08)}
.ph-pane .phl a:hover{color:#fff}
.ph-pane .phl .ico{width:15px;height:15px;flex:0 0 auto;color:var(--accent-fg)}

.phead .crumb{margin-bottom:13px}
.phead .eyebrow{margin-bottom:10px}
/* Its own scale rather than d1. The title is the tallest thing in the band, so
   it is the one that decides whether the content starts above the fold. It ran
   smaller than this while the band also had to carry two buttons. */
.ph-h1{font-size:clamp(2rem,3.7vw,3.05rem);line-height:1.06;max-width:26ch}
.phead .lede{margin-top:15px;max-width:70ch;font-size:1.0625rem}
.ph-meta{margin-top:18px}
.phead .meta{margin:0}
/* Shorter viewports get a shorter band, since half of 700px is not half of
   1000px and the promise is about the fold, not about pixels. */
@media (max-height:800px){
  .phead{padding-block:28px 26px}
  .ph-h1{font-size:clamp(1.75rem,3vw,2.5rem)}
  .phead .lede{font-size:1rem;margin-top:12px}
  .ph-meta{margin-top:14px}
}
@media (max-width:639px){
  .ph-h1{max-width:none}
  .phead .lede{margin-top:12px}
}

/* ---------- the use-case clip, now under the header ----------------------
   It used to sit beside the title. It reads better here anyway: the clip at a
   size worth watching, and the output list next to it instead of crammed under
   the copy. */
/* A 9:16 clip at 300px is 533px tall, and the six output chips beside it are
   three rows. Narrower clip, and the column beside it carries a heading, or the
   band is mostly the space around a tall video. */
/* The clip is 9:16, so it is tall whatever width it is given. Six chips beside
   it left most of the column empty; the whole output list in two columns is
   about the same height as the clip, which is what makes the band read as one
   block instead of a video with a caption stranded next to it. */
.ucshot{display:grid;grid-template-columns:minmax(0,262px) minmax(0,1fr);gap:clamp(24px,3.6vw,54px);align-items:center}
.ucshot-m{min-width:0}
.ucshot-t{display:grid;gap:16px;align-content:center;justify-items:start;min-width:0}
.ucshot-t h2{margin:0}
.ucshot-t .cap{margin:0}
.ucshot-t .grid-list{width:100%}
.gl-2{grid-template-columns:repeat(2,minmax(0,1fr))}
@media (max-width:959px){.gl-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:759px){
  .ucshot{grid-template-columns:minmax(0,1fr);justify-items:center;text-align:center}
  .ucshot-m{max-width:262px}
  .ucshot-t{justify-items:center}
  .ucshot-t .gl-item{text-align:left}
}
@media (max-width:479px){.gl-2{grid-template-columns:1fr}}

/* the same band when the example is landscape: picture above, list below */
.ucwide{max-width:min(900px,100%);margin-inline:auto}

/* A closing statement can carry a picture, not a wall. */
.kick-m{max-width:min(860px,100%);margin-inline:auto}

/* ---------- the workspace viewer ---------------------------------------
   Six tabs, a screenshot each. Radios, not buttons, so it works with no script
   and the arrow keys move between tabs for free.

   The tabs live in their own grid and the panels in another, with :has()
   correlating the two. The first version had inputs, labels and panels as one
   flat sibling list with `grid-row:1` on every label so they sat in one row --
   which forced the grid to invent implicit columns for labels 4 to 6 once the
   template dropped to three, and `grid-column:1/-1` on a panel then spanned
   only the three explicit ones. The panel came out 301px wide inside a 691px
   container. Two grids and :has() cannot do that to itself. */
.wsp-tabs{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:8px}
/* Not display:none: a hidden input is not focusable, and the tabs would stop
   being reachable by keyboard. */
.wsp-r{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;margin:0}
.wsp{position:relative}
.wsp-t{
  display:flex;flex-direction:column;gap:8px;align-items:flex-start;
  padding:13px 14px;border-radius:var(--r-chip);border:1px solid var(--line);
  background:var(--panel);cursor:pointer;
  font-family:'Space Grotesk',sans-serif;font-size:.8125rem;font-weight:600;
  letter-spacing:-.01em;line-height:1.25;color:var(--fg-2);
  transition:border-color .2s ease,color .2s ease,background-color .2s ease;
}
.wsp-t:hover{border-color:var(--line-2);color:var(--fg)}
.wsp-i{display:grid;place-items:center;width:30px;height:30px;border-radius:9px;background:var(--accent-dim);color:var(--accent-fg)}
.wsp-i .ico{width:16px;height:16px}
.wsp-r:checked+.wsp-t{border-color:var(--accent);color:var(--fg);background:var(--panel-2)}
.wsp-r:checked+.wsp-t .wsp-i{background:var(--accent);color:var(--accent-ink)}
.wsp-r:focus-visible+.wsp-t{outline:2px solid var(--accent);outline-offset:2px}

/* every panel in the same cell, one of them visible. The stage keeps the
   height of the tallest, so switching tabs does not move the page under the
   pointer. */
.wsp-stage{display:grid;margin-top:16px}
.wsp-p{
  grid-area:1/1;
  display:grid;grid-template-columns:minmax(0,1.32fr) minmax(0,1fr);
  gap:clamp(20px,3vw,44px);align-items:center;
  opacity:0;visibility:hidden;transition:opacity .28s ease;
}
.wsp:has(.wsp-r:nth-of-type(1):checked) .wsp-p:nth-of-type(1),
.wsp:has(.wsp-r:nth-of-type(2):checked) .wsp-p:nth-of-type(2),
.wsp:has(.wsp-r:nth-of-type(3):checked) .wsp-p:nth-of-type(3),
.wsp:has(.wsp-r:nth-of-type(4):checked) .wsp-p:nth-of-type(4),
.wsp:has(.wsp-r:nth-of-type(5):checked) .wsp-p:nth-of-type(5),
.wsp:has(.wsp-r:nth-of-type(6):checked) .wsp-p:nth-of-type(6){opacity:1;visibility:visible}
.wsp-t2{display:grid;gap:12px;align-content:center;justify-items:start;min-width:0}
.wsp-t2 p{margin:0}
@media (max-width:1023px){
  .wsp-tabs{grid-template-columns:repeat(3,minmax(0,1fr))}
  .wsp-p{grid-template-columns:minmax(0,1fr);gap:20px}
}
@media (max-width:559px){.wsp-tabs{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* a studio's features, two columns of hairline rows */
.fcols{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 clamp(22px,3.4vw,48px)}
@media (max-width:759px){.fcols{grid-template-columns:1fr}}

/* ---------- contact: the pitch panel beside the form --------------------
   The panel is one composed image, 864 by 1821, headline and bullets baked in.
   At the width a normal two-column split would give it, it would be 1300px tall
   against a 750px form. The column is fixed instead, at a width where the two
   end near each other, and both start at the top rather than being centred
   against each other. */
.cform{display:grid;grid-template-columns:minmax(0,400px) minmax(0,1fr);gap:clamp(28px,4vw,64px);align-items:start}
.cform-p{display:grid;gap:16px;justify-items:start}
.cform-m{
  margin:0;line-height:0;border-radius:var(--r-card);overflow:hidden;
  border:1px solid var(--line);box-shadow:var(--shadow);
}
.cform-m img{display:block;width:100%;height:auto}
.cform-p .small{margin:0}
/* One to two is 700px of picture before the form would even start, so on one
   column the form goes first and the panel follows it, capped. This is what the
   page it came from did, down to the 420px cap. */
@media (max-width:939px){
  .cform{grid-template-columns:minmax(0,1fr);gap:26px}
  .cform-f{order:1}
  .cform-p{order:2;justify-items:center;text-align:center}
  .cform-m{max-width:420px;margin-inline:auto}
}

/* ---------- Product Content, laid out as the CRO page it replaces --------
   Four things the rest of the site has no use for: a thin claim row under the
   header, a list whose marker is a cross, four steps with an arrow in each gap,
   and cards that carry a picture beside the copy rather than under it. */
.sec-thin{padding-block:clamp(30px,3.4vw,46px)}
/* The pricing header carries the page's h1, so it opens closer to the site
   header than a mid-page band would -- 14px of the first screen, which is the
   difference between the plan buttons being in it and not. */
.sec-thin[data-pricing]{padding-top:clamp(24px,2.4vw,32px)}
.lede .ac{color:var(--accent-fg)}

/* the problem list: .plist with the tick turned into a cross in a tinted disc */
.plist-x .ico{
  flex:0 0 auto;box-sizing:border-box;width:21px;height:21px;padding:4px;margin-top:1px;
  border-radius:50%;background:var(--accent-dim);color:var(--accent-fg);
}

/* Four steps with an arrow between them. The number is a disc in the corner, so
   it never competes with the title for the first line. Below the full row the
   arrows go: they are decorative, the numbers already carry the order, and two
   columns with arrows threaded between them is a puzzle worth nobody's time. */
.hiw{display:flex;align-items:stretch;gap:5px}
.hiw-s{
  flex:1;min-width:0;position:relative;display:grid;gap:9px;align-content:start;
  padding:24px 22px 26px;border-radius:var(--r-card);
  background:var(--panel-2);border:1px solid var(--line);
}
.hiw-s h3,.hiw-s p{margin:0}
/* The number sits in the top-right corner, so the heading keeps clear of it.
   Five cards across a laptop leaves each one narrow enough that the first line
   of a three-word heading reached the badge. */
.hiw-s h3{padding-right:30px}
.hiw-s .card-ico{margin-bottom:5px}
.hiw-n{
  position:absolute;top:15px;right:17px;display:grid;place-items:center;
  width:25px;height:25px;border-radius:50%;background:var(--accent);color:var(--accent-ink);
  font-family:'Space Grotesk',sans-serif;font-size:.75rem;font-weight:700;
}
.hiw-a{flex:0 0 auto;display:grid;place-items:center;color:var(--line-2)}
.hiw-a .ico{width:18px;height:18px}
@media (max-width:1023px){
  .hiw{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
  .hiw-a{display:none}
}
@media (max-width:559px){.hiw{grid-template-columns:1fr}}

/* The solution set: copy and a picture on every card, two up, the last across
   both. The picture keeps its own aspect ratio -- these five run from 1.56 to
   5.22 to one, so any fixed frame would crop the widest of them to a third. */
.solg{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
.solc{
  display:grid;grid-template-columns:minmax(140px,.85fr) minmax(0,1.15fr);gap:20px;align-items:center;
  padding:22px;border-radius:var(--r-card);background:var(--panel);border:1px solid var(--line);
}
.solc-t{display:grid;gap:9px;align-content:center}
.solc-t h3,.solc-t p{margin:0}
.solc-w{grid-column:span 2;grid-template-columns:minmax(180px,.5fr) minmax(0,1.5fr)}
/* An anchor left behind by a section that moved. It is a grid child wherever it
   lands, so it takes itself out of the flow rather than claiming a column. */
.solc>[aria-hidden][id]{position:absolute}
/* The wide card, when its picture is a small file. Letting the 1.5fr media
   column have its way would blow a 480px frame up to 850. */
.solc-w.solc-sm{grid-template-columns:minmax(0,1fr) minmax(0,380px)}
.solc-m{border-radius:12px;overflow:hidden;background:var(--bg-2);line-height:0}
.solc-m img{display:block;width:100%;height:auto}
@media (max-width:1023px){
  .solg{grid-template-columns:1fr}
  .solc,.solc-w{grid-column:auto;grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr)}
}
@media (max-width:639px){.solc,.solc-w{grid-template-columns:1fr;gap:16px}}

/* The film band: the title beside the player on a wide screen, above it on a
   narrow one. The player keeps its 16:9 and never grows past the film's own
   1280px, because a 720p file stretched to 1400 looks like a 720p file. */
.fw{display:grid;gap:18px;justify-items:center}
.fw-h{max-width:56ch;text-align:center}
.fw-h h2,.fw-h p{margin:0}
/* Capped at the film's own width. A 1280-wide file stretched across a 1440
   viewport looks like a 1280-wide file stretched across a 1440 viewport. */
.fw-v{width:100%;max-width:1080px;background:#000}
.fw-v video{width:100%;height:100%;display:block;object-fit:contain}

/* the numbers a team watches, as tiles: three across, the last one over two */
.ktiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:11px}
.ktile{padding:17px 16px;border-radius:12px;background:var(--panel-2);border:1px solid var(--line)}
.ktile .k{
  display:block;font-family:'Space Grotesk',sans-serif;font-size:1rem;font-weight:600;
  letter-spacing:-.015em;color:var(--fg);
}
.ktile .l{display:block;margin-top:4px;font-size:.8125rem;line-height:1.35;color:var(--fg-2)}
.ktile:last-child{grid-column:span 2}
@media (max-width:479px){.ktiles{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* ---------- Resources, set as an editorial page -------------------------
   One lead story with the headline set into the picture, then a river of cards
   newest first. No band per category: an article says which kind of piece it is
   on its own page, where somebody is already reading it, and a page split five
   ways by taxonomy reads as an archive rather than as something to read. */
.mkick{display:flex;flex-wrap:wrap;gap:4px 14px;font-family:var(--mono);font-size:.6875rem;letter-spacing:.04em;color:var(--fg-3);margin:0}
/* With no page header the breadcrumb sits on the bare page, directly above the
   lead. In the header it took its spacing from the stack it was in; here it has
   none of its own and was touching the card. */
.crumb+.mlead,.crumb+h1{margin-top:17px}

.mlead{
  position:relative;display:block;border-radius:var(--r-card);overflow:hidden;
  background:var(--panel);border:1px solid var(--line);box-shadow:var(--shadow);
  transition:transform .3s var(--ease),box-shadow .3s var(--ease);
}
.mlead:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg)}
.mlead-m{overflow:hidden;background:var(--panel-2)}
.mlead-m>img{display:block;width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease)}
.mlead:hover .mlead-m>img{transform:scale(1.03)}
/* the scrim sits over the picture and under the words, never over the words */
.mlead.has-m::after{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(to top,rgba(6,8,16,.94) 0%,rgba(6,8,16,.74) 34%,rgba(6,8,16,.18) 70%,rgba(6,8,16,0) 100%);
}
.mlead-t{display:grid;gap:12px;padding:clamp(22px,3vw,40px)}
.mlead.has-m .mlead-t{
  position:absolute;z-index:2;inset:auto 0 0 0;
  padding:clamp(24px,3.6vw,52px);max-width:min(760px,100%);
}
.mlead.has-m .mlead-t :is(h1,h2),.mlead.has-m .mlead-t .lede,.mlead.has-m .mkick,.mlead.has-m .mlead-cta{color:#fff}
.mlead.has-m .mlead-t .lede{color:rgba(255,255,255,.86)}
.mlead.has-m .mkick{color:rgba(255,255,255,.72)}
.mlead-t :is(h1,h2),.mlead-t .lede{margin:0}
.mlead-t .lede{max-width:60ch}
.mlead-cta{
  display:inline-flex;align-items:center;gap:8px;margin-top:4px;
  font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:600;color:var(--accent-fg);
}
.mlead-cta .ico{width:15px;height:15px;transition:transform .2s var(--ease)}
.mlead:hover .mlead-cta .ico{transform:translateX(3px)}
/* Below this width a 21:9 strip is 150px tall, which is not a picture and has
   no room for a headline in it. The picture gets a taller crop and the words
   move underneath. */
@media (max-width:859px){
  .mlead-m{aspect-ratio:3/2}
  .mlead.has-m::after{display:none}
  .mlead.has-m .mlead-t{position:static;max-width:none}
  .mlead.has-m .mlead-t :is(h1,h2),.mlead.has-m .mlead-t .lede,.mlead.has-m .mkick{color:inherit}
  .mlead.has-m .mlead-t .lede{color:var(--fg-2)}
  .mlead.has-m .mkick{color:var(--fg-3)}
  /* The words are on the page here, not on the picture, so everything the
     overlay turned white has to turn back -- including this, which was white on
     white below the breakpoint. */
  .mlead.has-m .mlead-cta{color:var(--accent-fg)}
}

/* Two large, then the rest smaller. The smaller row is flex, so the last card
   across grows to the edge rather than leaving a gap where one should be. */
/* Named -river, not -row: .mrow is already the model row on the Platform page,
   and that one is defined further down the file, so it quietly won. The cards
   came out 216, 908 and 216 pixels wide inside a grid they were never meant to
   be in. */
.mriver-big{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
.mriver{display:flex;flex-wrap:wrap;gap:16px}
.mcard{
  /* min-width:0 here would let a card whose picture has not arrived shrink to
     the width of its own headline, which is how one row came out 137, 490 and
     137 pixels wide. */
  /* Grow to fill the row, but never past the width of a large card. Four in the
     smaller row is three and then one, and without a ceiling that last one
     stretched to the full 1136 and turned its cover into a slab. */
  flex:1 1 300px;min-width:min(290px,100%);max-width:min(100%,560px);
  display:grid;grid-template-rows:auto 1fr;
  border-radius:var(--r-card);overflow:hidden;background:var(--panel);border:1px solid var(--line);
  transition:border-color .2s ease,transform .3s var(--ease),box-shadow .3s var(--ease);
}
.s-light .mcard{box-shadow:0 1px 2px rgba(11,16,32,.04)}
.mcard:hover{border-color:var(--accent);transform:translateY(-3px);box-shadow:var(--shadow)}
.mcard-m{min-width:0;overflow:hidden;background:var(--panel-2)}
.mcard-m>img{display:block;width:100%;height:100%;object-fit:cover;transition:transform .6s var(--ease)}
.mcard:hover .mcard-m>img{transform:scale(1.05)}
.mcard-t{padding:19px 22px 24px;display:grid;gap:9px;align-content:start}
.mcard-t h3,.mcard-t p{margin:0}
/* Two of the articles have no cover. A letter is an honest cover: there is no
   photograph, so none is invented. */
.mcard-no{
  display:grid;place-items:center;
  background:linear-gradient(150deg,var(--panel-2) 0%,var(--bg-2) 100%);
  border-bottom:1px solid var(--line);
}
.mcard-no span{
  font-family:'Space Grotesk',sans-serif;font-weight:700;letter-spacing:-.05em;
  font-size:clamp(3.25rem,7vw,5.5rem);line-height:1;color:var(--line-2);
}
@media (max-width:759px){.mriver-big{grid-template-columns:1fr}}

/* ---------- an article, with its own column beside it -------------------
   Which kind of piece it is, when it went out, who wrote it and how to send it
   on. It sticks, so the share buttons are still there at the bottom of a
   two-thousand-word tutorial rather than only at the top. */
/* The prose caps its own measure at 68ch, so a 1fr second column left 215px of
   nothing down the right-hand side. Sizing the column and centring the pair
   makes the same space symmetrical, which reads as a column rather than as a
   layout that ran out. */
.art{
  display:grid;grid-template-columns:minmax(0,320px) minmax(0,820px);
  gap:clamp(28px,4.4vw,68px);align-items:start;justify-content:center;
}
/* More room for the piece, asked for on 11 September 2026: the column goes
   from 700 to 820 so the cover and the pictures inside the article are 17%
   bigger, and the type goes up a step with the measure held at 72ch -- about
   85 characters a line, which is as wide as a line can get before the eye
   starts losing its place on the way back. */
.art .prose{max-width:72ch;font-size:1.125rem;gap:22px}
.artside{position:sticky;top:96px;display:grid;gap:24px}
.artmeta{display:grid;gap:15px;margin:0}
.artmeta>div{display:grid;gap:3px}
.artmeta dt{font-family:var(--mono);font-size:.6875rem;text-transform:uppercase;letter-spacing:.1em;color:var(--fg-3)}
.artmeta dd{margin:0;font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:500;letter-spacing:-.01em;color:var(--fg)}
.artmeta .artkind{color:var(--accent-fg)}
.share{display:grid;gap:10px;padding-top:20px;border-top:1px solid var(--line)}
.share-h{margin:0;font-family:var(--mono);font-size:.6875rem;text-transform:uppercase;letter-spacing:.1em;color:var(--fg-3)}
.share-b{display:flex;flex-wrap:wrap;gap:8px}
.sbtn{
  display:grid;place-items:center;width:38px;height:38px;padding:0;
  border-radius:var(--r-chip);border:1px solid var(--line);background:var(--panel);
  color:var(--fg-2);cursor:pointer;
  transition:border-color .2s ease,color .2s ease,background-color .2s ease,transform .2s var(--ease);
}
.sbtn:hover{border-color:var(--accent);color:var(--accent-fg);background:var(--panel-2);transform:translateY(-1px)}
.sbtn .ico{width:17px;height:17px}
/* The copy button holds both marks and swaps which one shows, rather than the
   script rewriting the inside of the button. */
.sbtn-copy{position:relative}
.sbtn-copy .ico:nth-of-type(2){display:none}
.sbtn-copy.is-done{border-color:var(--accent);color:var(--accent-fg)}
.sbtn-copy.is-done .ico:nth-of-type(1){display:none}
.sbtn-copy.is-done .ico:nth-of-type(2){display:block}
@media (max-width:939px){
  .art{grid-template-columns:minmax(0,1fr);gap:26px}
  .artside{position:static;top:auto;gap:18px;padding-bottom:22px;border-bottom:1px solid var(--line)}
  .artmeta{grid-template-columns:repeat(auto-fit,minmax(120px,auto));gap:14px 28px}
  .share{padding-top:0;border-top:0}
}

/* ---------- a regulation we hold ourselves to ----------------------------
   A seal, and deliberately not one that could pass for a certificate. The
   caveat line is part of the component rather than a footnote near it: no
   certification exists for a system like this, and a badge that implied
   otherwise would be a fabricated credential on the one page whose whole job
   is to be believable. */
.reg{
  display:flex;align-items:center;gap:clamp(14px,2vw,22px);
  padding:16px 20px 16px 16px;border-radius:var(--r-card);max-width:640px;
  border:1px solid var(--line);background:var(--panel);box-shadow:var(--shadow);
}
.reg-seal{
  position:relative;flex:0 0 auto;width:74px;height:74px;border-radius:50%;
  display:grid;place-items:center;gap:1px;text-align:center;
  border:2px solid var(--accent);color:var(--accent-fg);
  background:linear-gradient(180deg,rgba(255,46,126,.12),rgba(255,46,126,.03));
}
.reg-seal::before{
  content:"";position:absolute;inset:4px;border-radius:50%;
  border:1px dashed rgba(255,46,126,.45);
}
.reg-seal .ico{width:19px;height:19px;margin-bottom:-2px}
.reg-seal b{
  font-family:var(--mono);font-size:.5625rem;font-weight:400;
  letter-spacing:.1em;line-height:1.15;color:var(--accent-fg);
}
.reg-body{display:grid;gap:3px;min-width:0}
.reg-t{font-family:'Space Grotesk',sans-serif;font-size:1rem;font-weight:600;letter-spacing:-.015em;color:var(--fg)}
.reg-s{font-size:.875rem;line-height:1.4;color:var(--fg-2)}
.reg-n{font-size:.75rem;line-height:1.4;color:var(--fg-3);margin-top:3px}
@media (max-width:519px){
  .reg{flex-direction:column;align-items:flex-start;gap:14px;padding:16px}
  .reg-seal{width:62px;height:62px}
}

/* ---------- Covely Ads: the stage ----------------------------------------
   Three acts and a return on one clock. --dur is the whole cycle and every
   keyframe below is a percentage of it, so nothing can drift: act one runs to
   25%, act two to 50%, act three to 75%, the return closes it out. Change
   --dur and the entire choreography scales with it.

   The resting state is the finished state -- cards visible, channels lit, bars
   standing -- and every animation lives inside a no-preference query. So
   reduced motion and a page with no JavaScript both get the whole diagram,
   standing still, rather than a stage waiting for a cue that never comes. */
.ads-sec{overflow:hidden}
/* No horizontal padding of its own: the stage is inside wrap-wide now, which
   owns the page's gutter. Its old 4vw clamp was what put the card row outside
   the measure on any window wider than about 1500. */
.stage{--dur:15s;width:100%;padding:clamp(22px,3vw,40px) 0 0}
.stage-row{
  display:grid;
  grid-template-columns:minmax(0,1.06fr) clamp(44px,5vw,86px) minmax(0,1fr) clamp(44px,5vw,86px) minmax(0,1fr);
  gap:0;align-items:stretch;
}
.stg{
  position:relative;display:flex;flex-direction:column;gap:16px;
  padding:20px 20px 22px;border-radius:var(--r-card);
  border:1px solid var(--line);background:var(--panel);
  min-height:clamp(300px,38vh,430px);
}
.stg::after{
  content:"";position:absolute;inset:-1px;border-radius:inherit;
  border:1.6px solid var(--accent);opacity:0;pointer-events:none;
}
.stg-lbl{display:grid;gap:2px;font-family:'Space Grotesk',sans-serif}
.stg-lbl b{font-family:var(--mono);font-size:.6875rem;font-weight:400;letter-spacing:.1em;color:var(--accent-fg)}
.stg-lbl{font-size:1.0625rem;font-weight:600;letter-spacing:-.015em;color:var(--fg)}
.stg-lbl i{font-style:normal;font-family:var(--sans);font-size:.8125rem;font-weight:400;color:var(--fg-3)}

/* act one: the artefacts arrive */
/* Scattered, and more of them than are on screen at once, so the panel reads as
   creative that keeps arriving rather than three finished cards. Position and
   tilt come from the markup; what the animation changes is which are present. */
/* Clipped at the frame on purpose: a card crossing the edge reads as more
   arriving than fits, and it guarantees nothing ever escapes the panel. */
/* The scatter's own shape, not whatever height the row happens to give it.
   Every card is placed and sized in per-cent of this box, but its height came
   from a flex row while its width grew with the viewport -- so the lowest
   cards grew past the bottom and were cut in half by the overflow rule. The
   lowest one needs 0.73 of the width once its rotation is counted (56% down,
   25% wide at 4:5, turned 4 degrees about its centre), so the box is 0.76 of
   it and nothing is clipped at any width. */
.gen-scatter{position:relative;flex:1;min-height:210px;aspect-ratio:100/76;overflow:hidden;border-radius:10px}
.gcard{
  position:absolute;margin:0;left:var(--x);top:var(--y);width:var(--w);
  aspect-ratio:var(--ar,9/16);border-radius:10px;overflow:hidden;
  border:1px solid var(--line);background:#0C0F1C;
  box-shadow:0 16px 36px -22px rgba(0,0,0,.8);
  rotate:var(--r);
}
.gcard>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.gcard-tag{
  position:absolute;left:5px;bottom:5px;z-index:1;padding:2px 7px;border-radius:99px;
  background:rgba(10,12,20,.76);backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px);
  font-family:'Space Grotesk',sans-serif;font-size:.625rem;font-weight:600;color:#F3F4F8;
}

/* act two: the channels take it */
.pub-chs{display:grid;gap:7px;flex:1;align-content:center}
.pch{
  position:relative;display:flex;align-items:center;gap:10px;
  padding:9px 12px 9px 15px;border-radius:11px;
  border:1px solid var(--line);background:var(--bg);
}
.pch .ico{width:16px;height:16px;color:var(--accent-fg);flex:0 0 auto}
/* The channel's own mark when the file is there, and its published colour when
   it is not. A wordmark in the right colour identifies a channel; a drawing of
   someone's logo from memory only pretends to. */
.pch-logo{width:18px;height:18px;flex:0 0 auto;object-fit:contain}
.pch-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto;background:var(--ch,var(--accent));box-shadow:0 0 0 3px color-mix(in srgb,var(--ch,var(--accent)) 22%,transparent)}
.pch b{color:var(--fg)}
.pch::before{
  content:"";position:absolute;left:0;top:8px;bottom:8px;width:2px;border-radius:2px;
  background:var(--ch,var(--accent));opacity:.55;
}
.pch b{font-family:'Space Grotesk',sans-serif;font-size:.875rem;font-weight:600;letter-spacing:-.01em;color:var(--fg)}
.pch em{margin-left:auto;font-style:normal;font-family:var(--mono);font-size:.6875rem;color:var(--fg-3);white-space:nowrap}

/* act three: the budget moves */
/* Say it is money. A column of bars changing height could be anything. */
.opt-money{display:flex;align-items:center;gap:8px;margin-top:-2px}
.om-cur{
  display:inline-grid;place-items:center;width:26px;height:26px;border-radius:50%;
  border:1px solid var(--accent);background:rgba(255,46,126,.12);
  color:var(--accent-fg);font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:600;
}
.om-t{font-family:var(--mono);font-size:.6875rem;letter-spacing:.1em;text-transform:uppercase;color:var(--fg-3)}
.opt-bars{display:flex;align-items:stretch;gap:clamp(6px,.8vw,12px);flex:1;min-height:120px}
/* which way this channel's share went */
/* Visible at rest, so reduced motion still gets the direction the budget moved.
   The animation fades it in and out; the resting state is the finished one. */
.ob s{
  grid-row:2;text-decoration:none;text-align:center;font-size:.75rem;line-height:1;
  opacity:1;
}
.ob-up s{color:#7BE0A8}
.ob-down s{color:var(--fg-3)}
.ob{flex:1 1 0;min-width:0;display:grid;grid-template-rows:minmax(0,1fr) auto auto;gap:5px}
.ob i{
  align-self:end;width:100%;height:100%;border-radius:7px 7px 3px 3px;
  transform-origin:bottom;transform:scaleY(var(--a));
  background:linear-gradient(180deg,var(--accent),rgba(255,46,126,.42));
}
/* the bars stand in channel order, so each one says which channel it is */
.ob em{
  font-style:normal;font-family:var(--mono);font-size:.625rem;color:var(--fg-3);
  text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

/* the wires between the acts */
.conn{position:relative;display:grid;place-items:center}
.conn-line{position:absolute;top:50%;left:6px;right:6px;height:1px;background:var(--line-2)}
.conn .ico{position:relative;width:17px;height:17px;color:var(--line-2);background:var(--bg);border-radius:50%}
.conn-dot{position:absolute;top:50%;left:6px;width:calc(100% - 12px);height:0;opacity:0}
.conn-dot::before{
  content:"";position:absolute;left:0;top:0;width:9px;height:9px;margin:-4.5px 0 0 -4.5px;
  border-radius:50%;background:var(--accent);box-shadow:0 0 16px 4px rgba(255,46,126,.6);
}

/* the return */
.stage-back{position:relative;margin-top:16px;padding-top:19px;display:grid;place-items:center}
.sb-line{position:absolute;top:0;left:0;right:0;height:1px;background:var(--line-2)}
.sb-dot{position:absolute;top:0;right:0;width:100%;height:0;opacity:0}
.sb-dot::before{
  content:"";position:absolute;right:0;top:0;width:9px;height:9px;margin:-4.5px -4.5px 0 0;
  border-radius:50%;background:var(--accent);box-shadow:0 0 16px 4px rgba(255,46,126,.6);
}
.sb-t{
  display:inline-grid;place-items:center;width:34px;height:34px;border-radius:50%;
  border:1px dashed var(--line-2);background:var(--panel);
}
.sb-t .ico{width:15px;height:15px;color:var(--accent-fg);flex:0 0 auto}

@media (prefers-reduced-motion:no-preference){
  /* act one, 0-25%: each artefact lands, holds, then leaves for the channels */
  @keyframes stg-gc{
    0%{opacity:0;transform:translateY(14px) scale(.9)}
    9%{opacity:1;transform:none}
    58%{opacity:1;transform:none}
    70%{opacity:.14;transform:translateY(-10px) scale(.96)}
    92%,100%{opacity:0;transform:translateY(14px) scale(.9)}
  }
  /* act two, 25-50%: one channel at a time */
  @keyframes stg-pch{
    0%,22%{border-color:var(--line);background:var(--bg)}
    27%{border-color:var(--accent);background:var(--panel-2)}
    46%{border-color:var(--line-2);background:var(--panel-2)}
    56%,100%{border-color:var(--line);background:var(--bg)}
  }
  /* act three, 50-75%: budget leaves some bars and lands on others */
  @keyframes stg-ob{
    0%,48%{transform:scaleY(var(--a))}
    64%{transform:scaleY(var(--b))}
    88%{transform:scaleY(var(--b))}
    100%{transform:scaleY(var(--a))}
  }
  /* the wire between two acts fires as the hand-off happens */
  @keyframes stg-run{
    0%{transform:translateX(0);opacity:0}
    2%{opacity:1}
    17%{transform:translateX(100%);opacity:1}
    20%{transform:translateX(100%);opacity:0}
    100%{transform:translateX(100%);opacity:0}
  }
  @keyframes stg-arrow{0%,18%{color:var(--line-2)}24%{color:var(--accent)}40%{color:var(--line-2)}100%{color:var(--line-2)}}
  /* the return, 75-100%: right to left, back to where it started */
  @keyframes stg-back{
    0%,72%{transform:translateX(0);opacity:0}
    76%{opacity:1}
    96%{transform:translateX(calc(-100% + 9px));opacity:1}
    100%{transform:translateX(calc(-100% + 9px));opacity:0}
  }
  @keyframes stg-act{0%{opacity:0}3%{opacity:.9}20%{opacity:.35}26%{opacity:0}100%{opacity:0}}
  /* the direction marks appear with the budget move and leave with it */
  @keyframes stg-arrowfade{0%,50%{opacity:0}62%{opacity:1}86%{opacity:1}96%,100%{opacity:0}}

  /* Six cards on a cycle a sixth of the whole apart: at any moment some are
     arriving, some are holding, some are on their way out. */
  .is-in .gcard{animation:stg-gc var(--dur) ease-in-out infinite;animation-delay:calc(var(--n) * var(--dur) / -6)}
  .is-in .ob s{animation:stg-arrowfade var(--dur) linear infinite;animation-delay:calc(var(--n) * .16s)}
  .is-in .pch{
    animation:stg-pch var(--dur) linear infinite;
    animation-delay:calc(var(--n) * .5s + var(--dur) * .0);
  }
  .is-in .ob i{animation:stg-ob var(--dur) ease-in-out infinite;animation-delay:calc(var(--n) * .16s)}
  /* the two wires fire a quarter apart, at the two hand-offs */
  .is-in .conn-dot{
    animation:stg-run var(--dur) linear infinite;
    animation-delay:calc(var(--dur) * .21 + var(--n) * var(--dur) * .25);
  }
  .is-in .conn .ico{
    animation:stg-arrow var(--dur) linear infinite;
    animation-delay:calc(var(--dur) * .04 + var(--n) * var(--dur) * .25);
  }
  .is-in .sb-dot{animation:stg-back var(--dur) linear infinite}
  /* each panel glows as its own act comes round */
  .is-in .stg::after{
    animation:stg-act var(--dur) linear infinite;
    animation-delay:calc(var(--a) * var(--dur) * .25);
  }
}

@media (max-width:1099px){
  /* stacked, so the wires turn a quarter and run downward */
  .stage-row{grid-template-columns:minmax(0,1fr);gap:0}
  .stg{min-height:0}
  .conn{padding:13px 0;min-height:52px}
  .conn-line{top:6px;bottom:6px;left:50%;right:auto;width:1px;height:auto}
  .conn .ico{transform:rotate(90deg)}
  .conn-dot{top:6px;left:50%;width:0;height:calc(100% - 12px);margin-left:-.5px}
  .gen-scatter{max-width:460px;min-height:210px}
  .opt-bars{min-height:104px}
  @media (prefers-reduced-motion:no-preference){
    @keyframes stg-run-v{
      0%{transform:translateY(0);opacity:0}
      2%{opacity:1}
      17%{transform:translateY(100%);opacity:1}
      20%,100%{transform:translateY(100%);opacity:0}
    }
    .is-in .conn-dot{animation-name:stg-run-v}
    .is-in .conn .ico{animation:none;color:var(--line-2)}
  }
}
@media (max-width:519px){
  .stage{padding-inline:14px}
  .stg{padding:16px 15px 18px}
  .gen-scatter{min-height:170px}
  .pch em{display:none}
  .sb-t{font-size:.6875rem;padding:7px 13px}
}

/* ---------- one creative, five channels ---------- */
/* ---- Covely Ads: the loop that runs a campaign ----

   Replaces .fan, whose five wires missed four of the five rows they were drawn
   to. That happened because the SVG assumed the rows filled the connector's
   height and they did not, so this one does not assume: .optb-ch is five equal
   1fr tracks with no gap, stretched to the same height as .optb-w, and each
   row's visible card is inset by a symmetric margin so the track centre and
   the card centre are the same point. Row i's centre is (i+0.5)/5 of the box
   at any height, which is exactly what the paths are written in.

   Four beats on one --dur, every keyframe a percentage of it: the wires draw,
   a pulse runs out and the bars fill, a second pulse runs back, then the chip
   appears and the bars re-balance while the creative rotates. Nothing carries
   its own duration, so nothing can drift.

   Every animated element rests in its FINISHED state and the keyframes start
   from the unfinished one -- wires drawn, rows in place, bars at the
   reallocated split, chip shown. Under reduced motion, where none of these
   animations is applied, that resting state is what the reader gets: a
   complete, legible board rather than an empty one. */
.optb{
  --dur:16s;
  display:grid;
  grid-template-columns:minmax(0,.88fr) clamp(72px,8.4vw,128px) minmax(0,1.04fr);
  align-items:stretch;
}
/* the card stack, and nothing else in this column: a caption in the flow here
   would push the cards off the row's centre, which is where the wires start */
.optb-src{align-self:center;position:relative;display:grid}
.optb-c{grid-area:1/1;margin:0}
.optb-c .frame{box-shadow:var(--shadow-lg)}
.optb-c2{transform:translate(15px,-15px) scale(.94);opacity:.45}
.optb-c1{z-index:1}
/* top-left, not bottom-left: both creatives carry their own strapline across
   the foot of the frame and the pill was sitting on top of it */
.optb-tag{
  position:absolute;z-index:2;left:12px;top:12px;
  padding:5px 11px;border-radius:var(--r-pill);
  background:rgba(8,10,18,.62);backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);
  border:1px solid rgba(255,255,255,.18);color:#fff;
  font-family:'Space Grotesk',sans-serif;font-size:.75rem;font-weight:600;letter-spacing:-.01em;
}

.optb-w{align-self:stretch;position:relative;min-height:320px}
.optb-w svg{position:absolute;inset:0;width:100%;height:100%}
.optb-w path{fill:none;stroke-linecap:round}
.optb-p{stroke:var(--line-2);stroke-width:1.4;stroke-dasharray:1;stroke-dashoffset:0}
.optb-go,.optb-bk{stroke-width:2.6;stroke-dasharray:.09 .91;opacity:0}
.optb-go{stroke:var(--accent);stroke-dashoffset:0}
.optb-bk{stroke:var(--fg-3);stroke-dashoffset:1}

.optb-ch{align-self:stretch;position:relative;display:grid;grid-template-rows:repeat(5,minmax(54px,1fr))}
.optb-row{
  margin-block:4px;display:grid;grid-template-columns:auto minmax(0,1fr);
  align-content:center;gap:7px 11px;padding:10px 15px;
  border-radius:12px;border:1px solid var(--line);background:var(--panel);box-shadow:var(--shadow);
}
.optb-i{grid-row:1/3;align-self:center;display:grid;place-items:center;color:var(--accent-fg)}
.optb-i .ico{width:17px;height:17px}
.optb-hd{display:flex;align-items:baseline;gap:10px;min-width:0}
.optb-hd b{font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:.9375rem;letter-spacing:-.01em;color:var(--fg)}
.optb-fm{margin-left:auto;font-family:var(--mono);font-size:.7188rem;color:var(--fg-3);white-space:nowrap}
.optb-bar{display:block;height:5px;border-radius:99px;background:var(--line);overflow:hidden}
.optb-bar>i{display:block;height:100%;width:var(--b);border-radius:99px;background:var(--accent)}
/* the move is announced before it is applied, which is what the product does */
/* above the column, in the board's own top margin: the chip announces the
   move before it is applied, so reading top-down it has to come before the
   bars. It clears the section heading by 38px at 1440 and it is out of the
   flow, so it moves nothing. */
.optb-chip{
  position:absolute;z-index:3;right:6px;top:-15px;
  display:inline-flex;align-items:center;gap:7px;padding:6px 12px;border-radius:var(--r-pill);
  background:var(--accent);color:var(--accent-ink);box-shadow:var(--shadow);
  font-family:'Space Grotesk',sans-serif;font-size:.75rem;font-weight:600;letter-spacing:-.01em;
}
.optb-chip .ico{width:14px;height:14px}

@media (prefers-reduced-motion:no-preference){
  /* the stagger is a fraction of the cycle, not a fixed delay: every animation
     here is the same duration and infinite, so a phase shift stays a phase
     shift for as long as the page is open */
  .optb-p{animation:optb-draw var(--dur) var(--ease-out) infinite;animation-delay:calc(var(--n) * var(--dur) / 46)}
  .optb-go{animation:optb-go var(--dur) linear infinite;animation-delay:calc(var(--n) * var(--dur) / 46)}
  .optb-bk{animation:optb-bk var(--dur) linear infinite;animation-delay:calc(var(--n) * var(--dur) / 46)}
  .optb-row{animation:optb-in var(--dur) var(--ease-out) infinite;animation-delay:calc(var(--i) * var(--dur) / 46)}
  .optb-bar>i{animation:optb-fill var(--dur) var(--ease-out) infinite}
  .optb-chip{animation:optb-chip var(--dur) var(--ease-out) infinite}
  .optb-c1{animation:optb-front var(--dur) var(--ease-out) infinite}
  .optb-c2{animation:optb-behind var(--dur) var(--ease-out) infinite}
}
@keyframes optb-draw{0%,4%{stroke-dashoffset:1}20%,100%{stroke-dashoffset:0}}
@keyframes optb-go{
  0%,26%{stroke-dashoffset:1;opacity:0}
  30%{opacity:1}
  46%{stroke-dashoffset:0;opacity:1}
  50%,100%{stroke-dashoffset:0;opacity:0}
}
@keyframes optb-bk{
  0%,52%{stroke-dashoffset:0;opacity:0}
  56%{opacity:.9}
  68%{stroke-dashoffset:1;opacity:.9}
  72%,100%{stroke-dashoffset:1;opacity:0}
}
@keyframes optb-in{0%{opacity:0;transform:translateX(12px)}13%,95%{opacity:1;transform:none}100%{opacity:0;transform:translateX(12px)}}
@keyframes optb-fill{0%,16%{width:0}30%,64%{width:var(--a)}76%,94%{width:var(--b)}100%{width:0}}
@keyframes optb-chip{0%,64%{opacity:0;transform:translateY(7px)}70%,90%{opacity:1;transform:none}96%,100%{opacity:0;transform:translateY(7px)}}
@keyframes optb-front{
  0%,70%{transform:none;opacity:1;z-index:1}
  84%,94%{transform:translate(15px,-15px) scale(.94);opacity:.45;z-index:0}
  100%{transform:none;opacity:1;z-index:1}
}
@keyframes optb-behind{
  0%,70%{transform:translate(15px,-15px) scale(.94);opacity:.45;z-index:0}
  84%,94%{transform:none;opacity:1;z-index:1}
  100%{transform:translate(15px,-15px) scale(.94);opacity:.45;z-index:0}
}
@media (max-width:1023px){
  /* the connector goes rather than shrinking: five curves across 72px is five
     smudges. The card and the rows carry the argument on their own. */
  .optb{grid-template-columns:1fr;gap:clamp(24px,3.4vw,34px)}
  .optb-w{display:none}
  .optb-src{max-width:360px}
  .optb-ch{grid-template-rows:none;gap:8px}
  .optb-row{margin-block:0}
  .optb-chip{position:static;justify-self:start}
}

/* ---------- editorial list, for reading rather than scanning a table ---------- */
.edit{display:grid;grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr);gap:clamp(26px,3.4vw,52px);align-items:start}
@media (max-width:939px){.edit{grid-template-columns:1fr}}
.edit-lead{display:grid;gap:14px}
.edit-lead .frame{box-shadow:var(--shadow)}
.edit-rows{display:grid}
/* Three columns, not four: the picture and the kind stack in the first, the
   title and the date in the second, the arrow in the third. That is what lets
   the picture go from 72px to 132px without taking width off the headline. */
.edit-row{
  display:grid;grid-template-columns:132px minmax(0,1fr) auto;gap:16px;align-items:center;
  padding:18px 0;border-top:1px solid var(--line);
}
.edit-c{display:grid;gap:8px;justify-items:start;min-width:0}
.edit-b{display:grid;gap:5px;min-width:0}
/* The thumbnail. 4:3 and cropped, because the covers arrive at every ratio and
   a row of tiles that each pick their own height reads as a mistake. Sized in
   CSS as well as in the attributes so the row reserves the space before the
   picture arrives. The files are 288px wide, so 132 draws them at 2.2x. */
.edit-th{
  width:132px;aspect-ratio:4/3;border-radius:10px;overflow:hidden;
  background:var(--panel);border:1px solid var(--line);
}
.edit-th>img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .3s var(--ease)}
.edit-row:hover .edit-th>img{transform:scale(1.05)}
/* An article with no cover in the CRM, or one nobody has mirrored yet: a tile
   that reads as deliberately empty rather than as a picture that failed. The
   column still lines up, and nothing pretends to be an image. */
.edit-th-none{
  background:
    repeating-linear-gradient(135deg,
      color-mix(in srgb,var(--fg) 5%,transparent) 0 6px,
      transparent 6px 12px);
}
.edit-row:last-child{border-bottom:1px solid var(--line)}
.edit-k{font-family:'Space Grotesk',sans-serif;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.12em;color:var(--accent-fg)}
.edit-t{font-family:'Space Grotesk',sans-serif;font-size:1rem;font-weight:600;letter-spacing:-.015em;color:var(--fg);transition:color .18s ease}
/* The same headline in a card rather than a row: it has the width for it. */
.edit-t-lg{font-size:1.125rem;line-height:1.25}
a.card:hover .edit-t{color:var(--accent-fg)}
.edit-row .ico{color:var(--fg-3);transition:transform .22s var(--ease),color .18s ease}
.edit-row:hover .edit-t{color:var(--accent-fg)}
.edit-row:hover .ico{color:var(--accent-fg);transform:translate(3px,-3px)}
/* The date. Tabular figures so five rows of them line up rather than jitter by
   the width of a 1. */
.edit-d{font-size:.75rem;color:var(--fg-3);font-variant-numeric:tabular-nums}
@media (max-width:519px){
  /* The picture steps down rather than the columns rearranging: with the kind
     under it the first column is a block, and breaking that block up is what
     made the old three-row phone layout hard to read. */
  .edit-row{grid-template-columns:96px minmax(0,1fr) auto;gap:13px}
  .edit-th{width:96px}
}

/* A figure that is narrower than the band it sits in: for a picture whose own
   shape, not the column, decides how wide it should be. A square at a wide
   section's full width is a thousand pixels tall. */
.fig-c{margin:0 auto;max-width:min(100%,620px);display:grid;gap:12px}

/* ============ the showcase use-case template ============================
   A page that shows before it explains. Everything here exists because the
   argument is a clip: the wall in the hero, the format cards, the creator rail.

   One rule runs through all three: the poster is a real <img> under the
   <video>, both filling the same box. So the section is a wall of stills with
   no JavaScript, with the video bucket unreachable, or under reduced motion
   (covely.js will not fetch a decorative clip at all in that case), and the
   clip simply covers the still once it plays. Nothing ever paints an empty
   frame or a black rectangle. */
.vc{
  position:relative;margin:0;aspect-ratio:9/16;overflow:hidden;
  border-radius:var(--r-card);background:var(--panel);
  border:1px solid var(--line);box-shadow:var(--shadow);
}
.vc>img,.vc>video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}
/* Same contract as .clip and .vcard: the clip covers its poster only once it is
   really playing, never merely because a src has been set. A slow or blocked
   bucket then shows the still rather than an empty player, which is the whole
   reason the poster is a real <img> underneath. A promoted player — what
   covely.js gives a visitor who asked for reduced motion — is opaque on its own
   dark ground, because it is chrome the visitor asked for. */
.vc>video{opacity:0;transition:opacity .45s ease}
.vc>video.is-playing{opacity:1}
.vc>video[controls]{opacity:1;background:#0C0F1C}

/* The showcase hero's own furniture -- .uc-hero, .uch and its copy, chips and
   picture -- came out on 10 September 2026 with the tall header it dressed.
   Those five pages use the standard short .phead now, like every other page
   below the homepage.

   Removing it left the closing brace of the @media (min-width:1024px) that
   held .uc-hero's full-screen rule. A stray } at the top level is a parse
   error, and the way a browser recovers from one is to swallow the rule that
   follows it -- which was .fmt{display:grid}, so the six format cards on all
   five of those pages stacked one per row at the full measure. Nothing warned:
   the sheet still loaded, 1036 rules of 1037. */

/* ---- the formats: a claim and the clip that proves it, in one card ---- */
.fmt{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(14px,1.7vw,22px)}
.fmt-c{
  display:grid;grid-template-rows:auto 1fr;gap:0;overflow:hidden;
  border-radius:var(--r-card);background:var(--surface,var(--panel));
  border:1px solid var(--line);box-shadow:var(--shadow);
}
/* the clip is the card's top half, so its own frame chrome would double up */
.fmt-c .vc{
  aspect-ratio:4/5;border:0;border-radius:0;box-shadow:none;
}
/* A still is matted, not cropped.

   The clips are all 9:16, so a 4:5 box costs them nothing. The stills are not:
   they run from a 0.6 portrait creative to a 2:1 set of four ads, and cropping
   those to 4:5 cuts two of the four ads off and takes the headline off the
   banner. Since the point of the card is to show what shape the format arrives
   in, the shape is the one thing that cannot be cropped away — so every still
   sits whole inside the same box, on the card's own ground, and the box stays
   identical so a row of them still lines up. */
.fmt-c .vc-still{background:var(--panel)}
.fmt-c .vc-still>img{object-fit:contain;padding:14px}
.fmt-b{padding:18px 20px 20px}
.fmt-b .small{margin-top:6px;color:var(--fg-2)}
.fmt-also{color:var(--fg-3);max-width:78ch}
@media (max-width:939px){.fmt{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:559px){
  .fmt{grid-template-columns:1fr}
  /* One card per row, so the media box is the full width of the screen. A 9:16
     clip needs a squatter box at that width or a single card is a screenful on
     its own. A still does not: matting a portrait post into a landscape box
     here leaves it small between two wide mattes, so it takes its own shape
     instead and the card ends where the picture does. */
  .fmt-c .vc{aspect-ratio:16/10}
  .fmt-c .vc-still{aspect-ratio:auto}
  .fmt-c .vc-still>img{position:static;height:auto;padding:0}
}

/* a list of named jobs inside a card: the name leads, the sentence follows */
.alist{list-style:none;margin:11px 0 0;padding:0;display:grid;gap:9px}
.alist>li{position:relative;padding-left:15px;font-size:.9375rem;line-height:1.5;color:var(--fg-2)}
.alist>li::before{
  content:"";position:absolute;left:0;top:.6em;width:5px;height:5px;
  border-radius:50%;background:var(--accent);
}
.alist>li>b{color:var(--fg);font-weight:600}

/* ---- what do you want to create: two leads and four to follow ----

   Two half-width tiles on the first row, four quarter-width on the second, on a
   twelve-column grid. Every picture keeps its own 3:2 and takes the site's usual
   frame, so it inherits the wipe reveal every other picture gets.

   The anchor carries .rv and nothing else; .crt-in is the card. That split is
   what lets the hover lift exist at all: .rv owns transform, transition and a
   staggered transition-delay on the anchor, and writing a transition shorthand
   over it would reset the delay and take the stagger out.

   All of the movement is inside the reduced-motion query at the end of this
   block: the card lifts, the picture pushes in, the arrow fills with accent and
   an accent hairline wipes across the top edge. Every hover state is also a
   focus-visible state, because a card you can only open with a mouse is a card
   half the visitors cannot see working. */
/* stretch, not start: the four descriptions on the second row are different
   lengths, and left to their natural heights the row ended on a 23px step. The
   card's body is the 1fr track, so the slack lands under the text. */
.crt{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:clamp(14px,1.6vw,22px)}
.crt-t{grid-column:span 3;display:block;text-decoration:none;color:inherit;border-radius:var(--r-card)}
.crt-lead{grid-column:span 6}
.crt-in{
  position:relative;display:grid;grid-template-rows:auto 1fr;overflow:hidden;
  border-radius:var(--r-card);border:1px solid var(--line);
  background:var(--panel);box-shadow:var(--shadow);height:100%;
}
/* the picture is the card's top edge, so its own chrome would double up */
.crt-in .frame{border:0;border-radius:0;box-shadow:none}
.crt-a{
  position:absolute;top:10px;right:10px;z-index:2;
  width:34px;height:34px;border-radius:50%;display:grid;place-items:center;
  background:rgba(8,10,18,.46);border:1px solid rgba(255,255,255,.24);color:#fff;
  backdrop-filter:blur(6px);
}
.crt-a .ico{width:16px;height:16px}
.crt-b{padding:clamp(15px,1.4vw,19px) clamp(16px,1.5vw,21px) clamp(17px,1.6vw,22px)}
.crt-b .small{margin-top:6px;color:var(--fg-2)}
.crt-in::after{
  content:"";position:absolute;top:0;left:0;right:0;height:2px;z-index:3;
  background:linear-gradient(90deg,var(--accent),rgba(255,46,126,0));
  transform:scaleX(0);transform-origin:left;
}
@media (max-width:1023px){.crt-t,.crt-lead{grid-column:span 6}}
@media (max-width:599px){.crt-t,.crt-lead{grid-column:span 12}}

@media (prefers-reduced-motion:no-preference){
  .crt-in{transition:transform .5s var(--ease-out),box-shadow .5s var(--ease-out),border-color .4s ease}
  .crt-in>.frame>img{transition:transform 1.2s var(--ease-out)}
  .crt-a{transition:background .3s ease,border-color .3s ease,color .3s ease,transform .45s var(--ease-out)}
  .crt-in::after{transition:transform .55s var(--ease-out)}
  .crt-t:hover .crt-in,.crt-t:focus-visible .crt-in{
    transform:translateY(-5px);box-shadow:var(--shadow-lg);border-color:var(--line-2);
  }
  /* beats .rv.is-in .frame img{transform:none} on source order, same weight */
  .crt-t:hover .crt-in>.frame>img,.crt-t:focus-visible .crt-in>.frame>img{transform:scale(1.06)}
  .crt-t:hover .crt-a,.crt-t:focus-visible .crt-a{
    background:var(--accent);border-color:var(--accent);color:var(--accent-ink);transform:translate(2px,-2px);
  }
  .crt-t:hover .crt-in::after,.crt-t:focus-visible .crt-in::after{transform:scaleX(1)}
}

/* ---- the format card's furniture, on the one page that asks for it ----
   The channel pills sit on the title's line, the closing note under the grid
   with an accent rule instead of a box. Both come from a reference layout for
   the social page; a use case that does not set them renders the plain card. */
.fmt-t{display:flex;align-items:baseline;justify-content:space-between;gap:6px 12px;flex-wrap:wrap}
.fmt-tags{display:flex;gap:6px;flex-wrap:wrap}
.fmt-tag{
  padding:3px 9px;border-radius:999px;background:rgba(255,46,126,.09);color:var(--accent-fg);
  font-family:'Space Grotesk',sans-serif;font-size:.6875rem;font-weight:600;
  letter-spacing:.03em;white-space:nowrap;
}
.s-dark .fmt-tag{background:rgba(255,46,126,.16)}
.fmt-note{
  margin:clamp(20px,2.4vw,30px) 0 0;padding-left:15px;
  border-left:2px solid var(--accent);color:var(--fg-2);
  font-size:.9375rem;line-height:1.55;max-width:72ch;
}
/* the accent full stop: the sentence keeps its period, the period gets colour */
.hd-dot{color:var(--accent-fg)}

/* ---- the creator rail ---- */
.rost{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(12px,1.6vw,20px)}
.vc-r{aspect-ratio:4/5}
@media (max-width:719px){.rost{grid-template-columns:repeat(2,minmax(0,1fr))}}

.rost-still{margin:0;display:grid;gap:12px}

/* ---- five steps, on one rule ---- */
.stepr{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:clamp(14px,1.8vw,26px)}
.stepr>li{padding-top:18px;border-top:2px solid var(--line)}
.stepr-n{
  display:block;font-family:'Space Grotesk',sans-serif;font-size:.75rem;font-weight:600;
  letter-spacing:.14em;color:var(--accent-fg);margin-bottom:8px;
}
.stepr .d4{margin-bottom:5px}
.stepr .small{color:var(--fg-2)}
/* .stepr is built for five. Three in a five-column grid leaves 40% of the row
   empty, so the count picks the track list. */
.stepr-3{grid-template-columns:repeat(3,minmax(0,1fr))}
@media (max-width:1023px){.stepr{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:479px){.stepr{grid-template-columns:1fr}}

/* ---- one script, every placement ---- */
.spread{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(12px,1.6vw,20px)}
.spread-i{
  padding:22px;border-radius:var(--r-card);border:1px solid var(--line);
  background:var(--panel);display:grid;gap:6px;align-content:start;
}
.spread-i>b{font-family:'Space Grotesk',sans-serif;font-size:clamp(1.5rem,2.6vw,2.1rem);letter-spacing:-.03em;line-height:1}
.spread-i>span{font-size:.875rem;color:var(--fg-2)}
@media (max-width:719px){.spread{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* ---- video with the copy beside it ----
   The film used to sit in a band of its own with a single line under it, so
   there was nothing to read while it loaded and nothing to read if you did not
   press play. Beside a short account of the run it explains the product. The
   player leads on the wide layout because it is the thing being offered. */
.vt{display:grid;grid-template-columns:minmax(0,1.18fr) minmax(0,.82fr);gap:clamp(26px,3.4vw,52px);align-items:center}
.vt-v{margin:0}
.vt-t{display:grid;gap:14px;justify-items:start}
.vt-t .lede{max-width:44ch}
.vt-l{margin:0;padding:0;list-style:none;display:grid;gap:11px;counter-reset:vt}
.vt-l>li{
  position:relative;padding-left:34px;font-size:.9375rem;line-height:1.5;color:var(--fg-2);
  counter-increment:vt;
}
.vt-l>li::before{
  content:counter(vt);position:absolute;left:0;top:1px;
  width:22px;height:22px;border-radius:50%;display:grid;place-items:center;
  background:var(--accent);color:#fff;
  font-family:'Space Grotesk',sans-serif;font-size:.6875rem;font-weight:600;
}
.vt-l>li>b{color:var(--fg);font-weight:600}
@media (max-width:1023px){.vt{grid-template-columns:1fr;gap:30px}}

/* ---- a product capture at the full measure ----
   A screenshot of an interface is only worth showing if its own labels can be
   read, which means it gets the width and it is contained rather than cropped:
   a UI cropped to a ratio is a UI with its toolbar cut off. */
/* used with and without a caption, so it carries the gap either way */
/* The reset a <figure> needs -- the UA gives it 1em 40px -- without the top,
   which is the part that used to be here as `margin:0`. This rule sits 1500
   lines after the .mt-* utilities and weighs the same, so at equal specificity
   it won: every .shot that asked for mt-3 or mt-4 silently got the UA's 1em
   instead. Three did. :where() carries no specificity, so a bare .shot still
   has no top margin and a utility on one now applies. */
.shot{margin-inline:0;margin-bottom:0;display:grid;gap:12px}
:where(.shot){margin-top:0}
.shot .frame{background:#fff}
/* the steps under a picture rather than beside one: five across is right when
   they are the whole section, but under a board they read better tighter */
.stepr-d{gap:clamp(12px,1.5vw,22px)}
/* A centred figure that runs WIDER than .fig-c's 620px, not narrower — the
   board is the page's main picture and its panel labels have to be readable.
   Square, so this is also its height. */
.fig-wide{max-width:min(100%,760px)}
/* Wider again, for the board that IS a page's opening picture rather than an
   illustration inside a section. Measured, not chosen: the managed board is
   833px wide, and 900 draws it at 1.08x -- an upscale too small to show, and the
   widest it goes before the checklist text in it starts to soften. The page
   header's aside would have drawn it at 538, which is 0.65x of a picture whose
   smallest labels are already 12px. */
.fig-xw{max-width:min(100%,900px)}

/* ============ Signature Cast ============================================
   Four small additions rather than a page's worth of new CSS: the page is
   built from .stepr, .cards and .pane, which already exist. */

/* The one option worth pointing at, without a price to point with. */
.card-hi{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset,var(--shadow)}
.sc-tag{margin-top:12px;padding-top:12px;border-top:1px solid var(--line);color:var(--accent-fg);
  font-family:'Space Grotesk',sans-serif;letter-spacing:.06em;text-transform:uppercase}

/* Included / Not included / On Signature. A definition list, because that is
   what it is: three terms and what each one covers. The term sits above its
   definition on narrow screens and beside it once there is room. */
.deflist{margin:0;display:grid;gap:0}
.deflist>dt{
  font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:1.0625rem;
  letter-spacing:-.015em;color:var(--fg);padding-top:20px;border-top:1px solid var(--line);
}
.deflist>dd{margin:0 0 20px;padding-top:6px;color:var(--fg-2);max-width:78ch;font-size:.9375rem;line-height:1.6}
@media (min-width:860px){
  .deflist{grid-template-columns:minmax(0,200px) minmax(0,1fr);column-gap:clamp(24px,3vw,52px)}
  .deflist>dt{grid-column:1}
  .deflist>dd{grid-column:2;padding-top:20px;border-top:1px solid var(--line);margin-bottom:0}
}

/* The pricing page's one-line pointer at the product. */
.addon{
  display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;
  margin-top:clamp(20px,2.4vw,32px);padding:18px 20px;
  border:1px solid var(--line);border-radius:var(--r-card);background:var(--panel);
}
.addon>span{color:var(--fg-2);font-size:.9375rem}

/* ---- the digital twin, in four steps ----
   Rebuilt from the deck's process slide. Three stages across, then the eight
   outputs the twin covers — the point of the whole section is that last row,
   so it gets the width and the stages stay compact above it. */
.twin{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(16px,2.2vw,30px)}
.twin-s{
  position:relative;display:grid;gap:10px;align-content:start;
  padding:clamp(16px,1.8vw,22px);border-radius:var(--r-card);
  border:1px solid var(--line);background:var(--panel);
}
/* the stage the section is named after */
.twin-s-hi{border-color:var(--accent)}
.twin-n{
  font-family:'Space Grotesk',sans-serif;font-size:.6875rem;font-weight:600;
  letter-spacing:.14em;color:var(--accent-fg);
}
.twin-p{margin:0;display:grid;gap:8px}
/* the middle stage has no photograph: there is nothing to photograph about a
   model being fitted, so it gets the icon and the same box */
.twin-p-ico{
  aspect-ratio:4/5;border-radius:var(--r-card);border:1px dashed var(--line);
  background:var(--panel-2,var(--panel));place-content:center;justify-items:center;gap:18px;
}
/* The waveform the deck draws at this stage. Nineteen bars with their heights
   written in the markup: it is the audio half of what gets mapped, and it fills
   a box that otherwise read as a picture that had failed to load. */
.twin-wave{display:flex;align-items:center;gap:3px;height:64px}
.twin-wave>i{
  width:4px;height:var(--h);border-radius:2px;background:var(--accent);opacity:.55;
}
.twin-wave>i:nth-child(3n){opacity:.9}
.twin-tag{
  font-family:'Space Grotesk',sans-serif;font-size:.625rem;font-weight:600;
  letter-spacing:.12em;text-transform:uppercase;color:var(--fg-3);
}
.twin-s-hi .twin-tag{color:var(--accent-fg)}
.twin .d4{margin-top:2px}
@media (max-width:719px){.twin{grid-template-columns:1fr}.twin-p-ico{aspect-ratio:16/9}}

.twin-oh{max-width:34ch}
/* Eight outputs. Four across, then two rows — at eight in a row a face stops
   being a face, which is the one thing this row has to show. */
.twin-o{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:clamp(10px,1.4vw,18px)}
.twin-o>li{display:grid;gap:3px;min-width:0}
.twin-o>li>b{font-size:.875rem;margin-top:6px}
.twin-o>li>span{font-size:.75rem;color:var(--fg-3)}
@media (max-width:879px){.twin-o{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:559px){.twin-o{grid-template-columns:repeat(2,minmax(0,1fr))}}

/* ---------- the platform, drawn as layers ---------- */
.osmap{display:grid;gap:11px}
.oslayer{
  position:relative;display:grid;grid-template-columns:minmax(0,196px) minmax(0,1fr);gap:10px 20px;
  padding:19px 21px;border-radius:14px;border:1px solid var(--line);background:var(--panel);
}
.s-light .oslayer{box-shadow:0 1px 2px rgba(11,16,32,.04)}
.oslayer+.oslayer::before{content:"";position:absolute;top:-12px;left:38px;width:1px;height:11px;background:var(--line-2)}
.oslayer .k{display:block;font-family:'Space Grotesk',sans-serif;font-size:.6875rem;font-weight:600;text-transform:uppercase;letter-spacing:.12em;color:var(--accent-fg)}
.oslayer .t{display:block;font-family:'Space Grotesk',sans-serif;font-size:1.0625rem;font-weight:600;letter-spacing:-.015em;color:var(--fg);margin-top:3px}
.oslayer .d{display:block;font-size:.8125rem;line-height:1.45;color:var(--fg-3);margin-top:4px}
.oscells{display:flex;flex-wrap:wrap;gap:7px;align-content:center}
.oscell{padding:7px 12px;border-radius:9px;background:var(--panel-2);border:1px solid var(--line);font-size:.8125rem;color:var(--fg-2)}
.s-dark .oscell{background:rgba(255,255,255,.05)}
.oslayer.is-core{border-color:rgba(255,46,126,.3);background:linear-gradient(118deg,rgba(255,46,126,.08),var(--panel) 44%)}
.oslayer.is-core .oscell{border-color:rgba(255,46,126,.22)}
@media (max-width:767px){.oslayer{grid-template-columns:1fr}}

/* ---------- model rows, with a monogram in place of a vendor logo ---------- */
.mgroups{display:grid;gap:clamp(24px,3vw,38px);grid-template-columns:repeat(3,minmax(0,1fr))}
@media (max-width:939px){.mgroups{grid-template-columns:1fr}}
.mgroup-h{display:flex;align-items:center;gap:11px;margin-bottom:5px}
.mgroup{display:grid}
.mrow{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;align-items:start;padding:13px 0;border-top:1px solid var(--line)}
.mrow:first-of-type{border-top-color:var(--line-2)}
.mono{
  width:32px;height:32px;border-radius:9px;display:grid;place-items:center;flex:0 0 auto;
  background:var(--accent-dim);color:var(--accent-fg);
  font-family:'Space Grotesk',sans-serif;font-size:.8125rem;font-weight:700;
}
.mrow .nm{display:block;font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:1rem;letter-spacing:-.015em;color:var(--fg)}
.mrow .wh{display:block;font-size:.875rem;line-height:1.45;color:var(--fg-3);margin-top:2px}

/* ---------- newsletter, in the footer ---------- */
.ft-news{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:22px 48px;align-items:center;
  padding-bottom:clamp(32px,3.8vw,46px);margin-bottom:clamp(32px,3.8vw,46px);
  border-bottom:1px solid rgba(255,255,255,.09);
}
@media (max-width:767px){.ft-news{grid-template-columns:1fr}}
.ft-news .d3{color:#F3F4F8}
.ft-news .small{color:#8A92AC;margin-top:7px;max-width:54ch}
.news-row{display:flex;gap:8px;flex-wrap:wrap}
.news-row input{
  flex:1 1 210px;min-width:0;padding:12px 16px;border-radius:var(--r-pill);
  background:#0C0F1C;border:1px solid rgba(255,255,255,.18);color:#F3F4F8;font-size:.9375rem;
  transition:border-color .18s ease;
}
.news-row input::placeholder{color:#767E99;opacity:1}
.news-row input:focus{outline:none;border-color:var(--accent)}
.news-note{margin-top:10px;font-size:.75rem;line-height:1.5;color:#767E99}
.news-note a{color:#A9B0C6;text-decoration:underline;text-underline-offset:2px}
.news-note.is-err{color:#FFA3C6}
.news-note.is-ok{color:#F3F4F8}
.news.is-done .news-row{display:none}

/* ---------- looping clips, beside a hero ---------- */
.clip{position:relative}
/* poster in flow, clip stacked over it and faded in once it plays */
.clip>img{width:100%;height:100%;object-fit:cover}
.clip>video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .5s ease}
.clip>video.is-playing{opacity:1}
.clip>video[controls]{opacity:1;background:#0C0F1C}
.clip-badge{
  position:absolute;left:11px;bottom:11px;z-index:2;display:inline-flex;align-items:center;gap:7px;
  padding:6px 11px;border-radius:99px;background:rgba(10,12,20,.7);color:#F3F4F8;
  backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);
  font-family:'Space Grotesk',sans-serif;font-size:.6875rem;font-weight:600;letter-spacing:.07em;text-transform:uppercase;
}
.clip-badge .dot{width:6px;height:6px;border-radius:50%;background:var(--accent);flex:0 0 auto}
.hl-clip{grid-template-columns:minmax(0,1.34fr) minmax(0,.66fr);align-items:start}
.hl-clip .hero-copy{max-width:none;padding-top:0}
.hl-clip .lede{max-width:56ch}
.hl-clip>.anim-media{max-width:300px;margin-left:auto;width:100%}
/* 300px is a well-judged 9:16 clip and a starved 16:9 still: the same cap gives
   one a 533px height and the other 169px. A landscape still needs the width
   instead, so .hl-wide gives it an even share of the row and double the cap.

   The renderer sets the class rather than the CSS sniffing the ratio with
   :has(). A :has() selector carries the specificity of its argument, so
   .hl-clip:has(.anim-media .frame-16x9) outweighed the plain .hl-clip inside
   the stacking media query and the landscape hero never stacked: at 390px it
   held two columns and the image came out 158px wide. */
.hl-clip.hl-wide{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
.hl-clip.hl-wide>.anim-media{max-width:600px}
@media (max-width:1023px){
  .hl-clip>.anim-media{margin-inline:auto}
}
/* A wide still in half a 760px row is 340px of picture and a column of nothing,
   so it stacks well before a vertical clip needs to. */
@media (max-width:899px){
  .hl-clip.hl-wide{grid-template-columns:minmax(0,1fr)}
  .hl-clip.hl-wide>.anim-media{margin-inline:auto}
}
/* Two columns held on down to 320px, which left the copy in a 178px track next
   to a clip. Below 640 it stacks. */
@media (max-width:639px){
  .hl-clip{grid-template-columns:minmax(0,1fr)}
  .hl-clip>.anim-media{max-width:300px;margin-inline:auto}
  .hl-clip.hl-wide>.anim-media{max-width:600px}
}

/* ---------- trust row under a hero ---------- */
.trust{display:flex;flex-wrap:wrap;gap:14px 0;border-top:1px solid var(--line);padding-top:18px;margin-top:6px}
.trust>div{padding-right:clamp(18px,2.4vw,30px);margin-right:clamp(18px,2.4vw,30px);border-right:1px solid var(--line)}
.trust>div:last-child{border-right:0;margin-right:0;padding-right:0}
/* a wrapped row leaves hairlines hanging in mid air, so below the fold of a
   phone it becomes two columns with no dividers at all */
@media (max-width:639px){
  .trust{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px 18px}
  .trust>div,.trust>div:last-child{padding-right:0;margin-right:0;border-right:0}
}
@media (max-width:379px){.trust{grid-template-columns:1fr}}
.trust .t{display:block;font-family:'Space Grotesk',sans-serif;font-size:.9375rem;font-weight:600;letter-spacing:-.01em;color:var(--fg)}
.trust .d{display:block;font-size:.8125rem;color:var(--fg-3);margin-top:1px}

/* ---------- a video someone has to ask for ---------- */
.embed{position:relative;border-radius:var(--r-card);overflow:hidden;border:1px solid var(--line);background:#0C0F1C;aspect-ratio:16/9}
.embed>img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:.9}
.embed>iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.embed-btn{
  position:absolute;inset:0;z-index:2;display:grid;place-items:center;gap:12px;align-content:center;
  background:linear-gradient(180deg,rgba(10,12,20,.25),rgba(10,12,20,.65));
  border:0;cursor:pointer;color:#fff;width:100%;
}
.embed-btn .ring{
  width:64px;height:64px;border-radius:50%;background:var(--accent);color:#fff;display:grid;place-items:center;
  box-shadow:0 18px 40px -16px rgba(255,46,126,.7);transition:transform .25s var(--ease);
}
.embed-btn .ring .ico{width:26px;height:26px;margin-left:3px}
.embed-btn:hover .ring{transform:scale(1.07)}
.embed-cap{font-family:'Space Grotesk',sans-serif;font-size:.875rem;font-weight:600;color:#fff;text-shadow:0 1px 12px rgba(0,0,0,.6)}
.embed-note{margin-top:10px;font-size:.8125rem;color:var(--fg-3)}



/* ---------- before and after ----------
   One image under another, the top one clipped to wherever the divider is. The
   divider position is a custom property the range input writes, so the whole
   thing works from the keyboard and needs no pointer maths. */
.ba{
  --x:50%;
  position:relative;border-radius:var(--r-card);overflow:hidden;
  border:1px solid var(--line);background:var(--panel);box-shadow:var(--shadow);
  isolation:isolate;
}
.ba>.cap{position:static;padding:14px 18px 16px;margin:0;background:var(--panel);border-top:1px solid var(--line)}
/* the containing block for every overlay, sized by the base image alone */
.ba-media{position:relative;overflow:hidden;line-height:0}
.ba-base,.ba-top img{display:block;width:100%;height:auto}
/* The generated version sits on the right of the divider, which is the way a
   before-and-after reads and what the caption says. Clip the overlay from the
   left edge in, so the base image is what shows on the left. */
.ba-top{position:absolute;inset:0;clip-path:inset(0 0 0 var(--x))}
.ba-top img{height:100%;object-fit:cover;object-position:left top}
.ba-line{
  position:absolute;top:0;bottom:0;left:var(--x);width:2px;z-index:2;
  background:var(--accent);transform:translateX(-1px);pointer-events:none;
}
.ba-grip{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:44px;height:44px;border-radius:50%;display:grid;place-items:center;
  background:var(--accent);color:#fff;box-shadow:0 10px 26px -10px rgba(255,46,126,.8);
}
.ba-grip .ico{width:20px;height:20px}
/* The two labels. They were the same grey pill twice, which told you which side
   was which and nothing else. Now the state each one names is in its colour: the
   original is a muted glass chip, the generated side takes the accent the
   divider is already drawn in, so the eye ties the label to the line it belongs
   to. Both carry a dot, which is what makes a word this short read as a status
   rather than as a caption. */
.ba-tag{
  position:absolute;top:15px;z-index:2;
  display:inline-flex;align-items:center;gap:7px;
  padding:6px 13px;border-radius:99px;
  font-family:'Space Grotesk',sans-serif;font-size:.75rem;font-weight:600;
  letter-spacing:.1em;text-transform:uppercase;line-height:1.1;
}
.ba-tag::before{content:"";flex:0 0 auto;width:7px;height:7px;border-radius:50%}
.ba-tag-l{
  left:15px;background:rgba(10,12,20,.66);color:rgba(243,244,248,.94);
  border:1px solid rgba(255,255,255,.18);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
}
.ba-tag-l::before{background:rgba(255,255,255,.5)}
.ba-tag-r{
  right:15px;background:var(--accent);color:#fff;border:1px solid var(--accent);
  box-shadow:0 10px 26px -14px rgba(255,46,126,.95);
}
.ba-tag-r::before{background:rgba(255,255,255,.92)}
@media (max-width:559px){
  .ba-tag{top:10px;padding:4px 10px;font-size:.6875rem;gap:6px}
  .ba-tag-l{left:10px}
  .ba-tag-r{right:10px}
}
/* The control fills the image area, invisible but fully operable. A range with
   appearance:none has no intrinsic height, so without one stated here the input
   is zero pixels tall: keyboard-reachable and impossible to drag. */
.ba-range{
  position:absolute;inset:0;z-index:3;margin:0;padding:0;
  width:100%;height:100%;background:none;border:0;cursor:ew-resize;
  -webkit-appearance:none;appearance:none;opacity:0;
}
/* A narrow thumb keeps the value linear across the full width, so the divider
   lands under the finger instead of up to half a thumb away from it. */
.ba-range::-webkit-slider-thumb{-webkit-appearance:none;width:2px;height:100%}
.ba-range::-moz-range-thumb{width:2px;height:100%;border:0;background:none}
.ba:has(.ba-range:focus-visible) .ba-grip{outline:3px solid var(--fg);outline-offset:3px}
@media (prefers-reduced-motion:no-preference){
  .ba-grip{transition:transform .2s var(--ease)}
  .ba:has(.ba-range:active) .ba-grip{transform:translate(-50%,-50%) scale(.94)}
}
/* the divider never animates: it has to sit exactly where the finger is, and
   the one-time demonstration sweep is driven frame by frame in script instead */
.ba-line,.ba-top{transition:none}
/* While that sweep runs, the grip is what the eye should follow — a little
   larger, a little brighter, and the range is out of the way so a pointer
   already resting on the figure cannot half-take the handle mid-sweep. Both
   revert the moment it ends or somebody interrupts it. */
@media (prefers-reduced-motion:no-preference){
  .ba.is-demo .ba-grip{transform:translate(-50%,-50%) scale(1.12);box-shadow:0 14px 34px -8px rgba(255,46,126,.95)}
  .ba.is-demo .ba-range{pointer-events:none}
  .ba.is-demo .ba-tag{opacity:.55;transition:opacity .3s ease}
}
@media (max-width:519px){
  .ba-grip{width:38px;height:38px}
  .ba-tag{font-size:.625rem;padding:4px 9px}
}


/* ==========================================================================
   Motion layer
   Entrances and reveals are CSS, so they run off the main thread and stay
   smooth while the page is still loading. Scroll-linked effects use native
   scroll-driven animations behind @supports. Reduced motion keeps the fades
   and drops every movement.
   ========================================================================== */

/* ---------- kinetic display type: words are split at build time ---------- */
.words .w{
  display:inline-block;vertical-align:top;overflow:hidden;
  padding-bottom:.14em;margin-bottom:-.14em;   /* room for descenders */
}
.words .w>span{display:inline-block}
@media (prefers-reduced-motion:no-preference){
  .words .w>span{transform:translateY(105%)}
  .hero .words .w>span,
  .words.is-in .w>span{animation:wordUp .9s var(--ease-out) both;animation-delay:calc(var(--w,0)*45ms + var(--d,0ms))}
  @keyframes wordUp{from{transform:translateY(105%)}to{transform:translateY(0)}}
}

/* ---------- media reveal: a clip wipe plus a settling scale ----------
   The wipe is applied to the media, never to the figure that carries .rv.
   A clipped element reports an intersection ratio of zero even at threshold 0,
   so an IntersectionObserver never sees it arrive and it stays hidden for good.
   That shipped once, as a blank box where the image should be, on eleven pages. */
@media (prefers-reduced-motion:no-preference){
  .frame.rv{opacity:1;transform:none}
  .frame.rv>img,.frame.rv>video{
    clip-path:inset(0 0 100% 0);
    transition:clip-path 1s var(--ease-out),transform 1.2s var(--ease-out);
  }
  .frame.rv.is-in>img,.frame.rv.is-in>video{clip-path:inset(0 0 0 0)}
  .frame img,.frame video{transition:transform 1.2s var(--ease-out)}
  .rv .frame img,.frame.rv img{transform:scale(1.06)}
  .rv.is-in .frame img,.frame.rv.is-in img{transform:none}
  .frame-contain img{transform:none !important}   /* no crop room to hide it */
}

/* ---------- scroll-linked depth, native and progressive ---------- */
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference) and (min-width:1024px){
    [data-parallax]{
      animation:parallax linear both;
      animation-timeline:view();
      animation-range:entry 0% exit 100%;
    }
    @keyframes parallax{
      from{transform:translate3d(0,var(--par,26px),0)}
      to{transform:translate3d(0,calc(var(--par,26px) * -1),0)}
    }
  }
}

/* ---------- reading progress, on long documents ---------- */
.progress{
  position:fixed;top:0;left:0;right:0;height:2px;z-index:70;
  background:var(--accent);transform:scaleX(0);transform-origin:0 50%;pointer-events:none;
}
@supports (animation-timeline:scroll()){
  @media (prefers-reduced-motion:no-preference){
    .progress{animation:readProgress linear both;animation-timeline:scroll(root block)}
    @keyframes readProgress{from{transform:scaleX(0)}to{transform:scaleX(1)}}
  }
}

/* ---------- spotlight that follows the cursor across a card ---------- */
@media (hover:hover) and (pointer:fine){
  a.card{--mx:50%;--my:0%}
  a.card::before{
    content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;z-index:0;
    opacity:0;transition:opacity .28s ease;
    background:radial-gradient(340px circle at var(--mx) var(--my),var(--accent-dim),transparent 62%);
  }
  a.card:hover::before{opacity:1}
  /* Lifting the children above the glow re-positioned the one child that was
     absolute: a.card>* outweighs .card-arrow, so every linked card's arrow had
     been sitting in the flow at the bottom-left instead of the top-right
     corner it is written for. The z-index is what this rule is for; the arrow
     keeps its own positioning. */
  a.card>*{position:relative;z-index:1}
  a.card>.card-arrow{position:absolute}
}

/* ---------- the hero crossfade, blurred through the handover ---------- */
@media (prefers-reduced-motion:no-preference){
  @keyframes cyc{
    0%{opacity:0;filter:blur(6px)}
    2%,23%{opacity:1;filter:blur(0)}
    27%{opacity:0;filter:blur(6px)}
    100%{opacity:0;filter:blur(6px)}
  }
}

/* ---------- the output strip leans into scroll velocity ---------- */
.marq{transform:skewY(calc(var(--vel,0) * .05deg));transition:transform .35s var(--ease-out)}
@media (prefers-reduced-motion:reduce){.marq{transform:none}}

/* ---------- smooth scroll, only while Lenis is actually driving ---------- */
html.lenis,html.lenis body{height:auto}
.lenis:not(.lenis-autoToggle).lenis-stopped{overflow:clip}
.lenis.lenis-smooth{scroll-behavior:auto !important}
.lenis.lenis-smooth iframe{pointer-events:none}
.lenis [data-lenis-prevent]{overscroll-behavior:contain}

/* ---------- cross-document view transitions ---------- */
@view-transition{navigation:auto}
.hdr{view-transition-name:site-header}
.cc{view-transition-name:cookie-bar}
/* names have to be unique per document, and .cc already took cookie-bar */
.lb{view-transition-name:lang-bar}
::view-transition-old(root),::view-transition-new(root){
  animation-duration:.34s;animation-timing-function:cubic-bezier(.23,1,.32,1);
}
@media (prefers-reduced-motion:reduce){
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none !important}
}


/* ---------- one dash travelling down each wire ---------- */
@media (prefers-reduced-motion:no-preference){
  /* stroke-dashoffset repaints rather than composites, so it only runs once the
     block has been revealed: an endless repaint of an off-screen SVG is a
     battery cost with nobody watching it. */
  .fan-wires .pulse{stroke:var(--accent);stroke-width:2.4;stroke-dasharray:.05 .95;stroke-dashoffset:1}
  .rv.is-in .fan-wires .pulse,.is-in .fan-wires .pulse{
    animation:flow 3.4s linear infinite;animation-delay:calc(var(--n,0)*.4s);
  }
  @keyframes flow{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
}
@media (prefers-reduced-motion:reduce){.fan-wires .pulse{display:none}}

/* ---------- the live dot on a looping clip ---------- */
@media (prefers-reduced-motion:no-preference){
  .clip-badge .dot{animation:pulseDot 2.2s ease-in-out infinite}
  @keyframes pulseDot{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.82)}}
}

/* ---------- the gallery leans into the scroll like the strip does ---------- */
.vgal .marq-track{will-change:auto}

/* ---------- reduced motion: keep the fades, drop the movement ---------- */
@media (prefers-reduced-motion:reduce){
  .words .w{overflow:visible}
  .words .w>span,.frame.rv,.frame img,.frame video{transform:none;clip-path:none}
  .frame.rv>img,.frame.rv>video{clip-path:none}
  .rv{transform:none}
  .tri-col::before,.fan-wires path{transform:none;stroke-dashoffset:0}
}


/* ---------- the Enterprise enquiry: one question at a time ----------
   Typeform's shape, and the reasons it works: one question fills the screen,
   so there is nothing to compare it against and no sense of a form to get
   through; the question is set at display size because it is the only thing
   being said; and the input is a line rather than a box, because a box asks
   to be filled while a line asks to be answered.

   min-height and not height: a validation message or a three-line question on
   a phone makes the step taller, and clipping the button would be the one
   unforgivable bug in a form. */
.tf-sec{padding-block:clamp(28px,4vw,56px)}
.tf-t{margin-top:14px}
.tf-ld{max-width:52ch;margin-top:10px}
.tf{display:block;margin-top:clamp(14px,2vw,26px);position:relative}
.tf-l{list-style:none;margin:0;padding:0}
/* Progress with no rail behind it: the grey line across the top of the form
   was the one thing on the page that was neither a question nor an answer. */
.tf-p{height:2px;border-radius:2px;background:none;overflow:hidden}
.tf-p>span{display:block;height:100%;background:var(--accent);border-radius:2px;transition:width .4s var(--ease)}
.tf-s{
  min-height:min(52svh,420px);display:flex;flex-direction:column;justify-content:center;
  gap:10px;padding-block:clamp(22px,3vw,40px);
}
/* Load-bearing, and the house pattern (see .cc[hidden]): display:flex above
   outweighs the browser's own [hidden]{display:none}, which is a class beating
   a UA rule. Without this line every step is on screen at once and "one
   question at a time" is just a sentence in a commit message. Caught by
   reading getComputedStyle rather than by looking, because the steps below the
   first were off the bottom of the screenshot. */
.tf-s[hidden]{display:none}
.tf-n{
  display:flex;align-items:center;gap:9px;
  font-family:var(--mono);font-size:.75rem;letter-spacing:.12em;text-transform:uppercase;color:var(--fg-3);
}
.tf-n>span{
  display:grid;place-items:center;min-width:22px;height:22px;padding-inline:6px;
  border-radius:7px;background:var(--accent-dim);color:var(--accent-fg);
  font-size:.6875rem;letter-spacing:.04em;
}
.tf-n>span .ico{width:13px;height:13px}
.tf-n em{font-style:normal;color:var(--fg-3);text-transform:none;letter-spacing:0;font-family:var(--sans)}
.tf-q{
  display:block;font-family:'Space Grotesk',sans-serif;font-weight:600;letter-spacing:-.03em;line-height:1.08;
  font-size:clamp(1.75rem,4.4vw,3rem);color:var(--fg);
}
.tf-q-sm{font-size:clamp(1.1rem,2vw,1.375rem);letter-spacing:-.02em;margin-top:26px}
.tf-h{max-width:56ch;font-size:.9375rem;line-height:1.5;color:var(--fg-2)}
/* A line, not a box. The 1.5rem is what stops it reading as a form field. */
.tf-in{
  width:100%;margin-top:10px;padding:10px 2px;
  font-family:var(--sans);font-size:clamp(1.125rem,2.2vw,1.5rem);line-height:1.4;color:var(--fg);
  background:none;border:0;border-bottom:2px solid var(--line-2);border-radius:0;
  transition:border-color .2s ease;
}
.tf-in::placeholder{color:var(--fg-3);opacity:.75}
.tf-in:focus{outline:none;border-bottom-color:var(--accent)}
.tf-in[aria-invalid="true"]{border-bottom-color:var(--accent)}
textarea.tf-in{resize:vertical;min-height:3.4em}
.tf-e{font-size:.875rem;color:var(--accent-fg)}
.tf-a{display:flex;flex-wrap:wrap;align-items:center;gap:14px;margin-top:22px}
.tf-k{font-size:.8125rem;color:var(--fg-3)}
.tf-k kbd{
  font-family:var(--mono);font-size:.75rem;padding:2px 6px;border-radius:5px;
  background:var(--panel-2);border:1px solid var(--line);
}
.tf-b{
  display:inline-flex;align-items:center;gap:5px;margin-left:auto;padding:6px 2px;
  background:none;border:0;cursor:pointer;font-family:var(--sans);font-size:.875rem;color:var(--fg-3);
}
.tf-b:hover{color:var(--fg)}
.tf-b .ico{width:15px;height:15px;transform:rotate(180deg)}
.tf-done{justify-content:flex-start}
.tf-fb{margin-top:22px;font-size:.9375rem;color:var(--fg-2)}
@media (max-width:559px){
  .tf-s{min-height:auto}
  .tf-b{margin-left:0}
}

/* With no script there is no "one at a time": every step is shown, stacked and
   compact, and the buttons that would advance it are gone. The five questions
   are still readable and the email address is right there, which is the only
   honest fallback for a form that cannot submit without JavaScript. */
.no-js .tf-s[hidden]{display:flex}
.no-js .tf-s{min-height:auto;padding-block:16px;border-top:1px solid var(--line)}
.no-js .tf-q{font-size:clamp(1.125rem,2.4vw,1.5rem)}
.no-js .tf-a,.no-js .tf-p{display:none}
.no-js .tf-done{display:none}

/* --- .wire: one key, one call, every engine ---------------------------
   The diagram on /product/mcp-ai/. A row -- the call, a rail, the key, a rail,
   the engines -- and under it the strip of what comes back, on a dark panel,
   because the two code blocks further down the page are dark and this is the
   same subject drawn instead of written.

   Everything is drawn at rest. The animation is a pulse travelling through it:
   each part lights for about a second as the pulse reaches it, and the five
   results land one after another at the end of the sweep. A chip returns to
   exactly the state it started in, so most of this needs one keyframes and a
   --d per element; the results, which have to arrive rather than blink, use an
   infinite animation with a positive delay, so each one re-lands in time with
   the pulse that comes round to it. */
.wire{
  --dur:9s;
  --w-line:rgba(255,255,255,.14);
  --w-dim:#9AA2BC;
  --w-fg:#E4E7F2;
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(30px,.24fr) auto minmax(30px,.24fr) minmax(0,1fr);
  align-items:center;gap:clamp(10px,1.4vw,20px);
  padding:clamp(18px,2.4vw,30px);
  border-radius:var(--r-card);border:1px solid var(--line);
  background:var(--code-bg);color:var(--w-fg);
}
.wire-l{
  display:block;font-family:var(--mono);font-size:.6875rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--w-dim);margin-bottom:8px;
}
.wire-sub{display:block;font-family:var(--mono);font-size:.6875rem;color:var(--w-dim);margin-top:8px}
.wire-n{min-width:0}
/* One border and one radius on every chip, so the pulse can light any of them
   with the same keyframes. */
.wire-call code,.wire-key,.wire-eng li{
  border:1px solid var(--w-line);border-radius:var(--r-chip);background:rgba(255,255,255,.03);
}
.wire-call code{
  display:block;padding:11px 14px;font-family:var(--mono);
  font-size:clamp(.75rem,1vw,.875rem);color:var(--w-fg);overflow-x:auto;
}
.wire-mid{text-align:center}
.wire-key{
  display:block;padding:11px 18px;font-size:clamp(1rem,1.3vw,1.125rem);
  letter-spacing:-.01em;color:var(--w-fg);border-color:rgba(255,46,126,.5);
}
.wire-eng ul{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}
.wire-eng li{padding:5px 9px;font-family:var(--mono);font-size:.6875rem;color:var(--w-dim)}
.wire-eng li.wire-more{border-style:dashed}
/* What comes back, across the whole panel: the last beat is the one worth the
   room. */
.wire-out{grid-column:1/-1;text-align:center;margin-top:clamp(6px,1vw,12px);position:relative}
.wire-out .wire-l{margin-top:12px}
.wire-strip{display:flex;flex-wrap:wrap;gap:clamp(7px,.8vw,11px);justify-content:center;align-items:flex-end}
/* Sized by height, so five different ratios line up on one baseline. */
.wire-t{
  position:relative;margin:0;height:clamp(96px,11.5vw,158px);aspect-ratio:var(--ar,1/1);
  max-width:100%;border-radius:10px;overflow:hidden;border:1px solid var(--w-line);
  background:rgba(255,255,255,.04);
}
.wire-t>img,.wire-t>video{width:100%;height:100%;object-fit:cover;display:block}
.wire-t>.wire-v,.wire-t>video{position:absolute;inset:0}
.wire-t>.wire-v{opacity:0}
.wire-t>video{opacity:0;transition:opacity .5s ease}
.wire-t>video.is-playing{opacity:1}
.wire-ar{
  position:absolute;left:6px;bottom:6px;padding:2px 6px;border-radius:6px;
  font-family:var(--mono);font-size:.625rem;letter-spacing:.04em;color:var(--w-fg);
  background:rgba(6,9,18,.62);backdrop-filter:blur(3px);
}
/* The rails. Grey at rest; the accent sweep lives in the child so the line
   itself never moves. */
.wire-r,.wire-down{position:relative;border-radius:2px;background:var(--w-line)}
.wire-r{height:2px}
.wire-down{display:block;width:2px;height:56px;margin:-34px auto 0}
.wire-r>i,.wire-down>i{
  position:absolute;inset:0;border-radius:inherit;opacity:0;background-repeat:no-repeat;
}
.wire-r>i{
  background-image:linear-gradient(90deg,transparent,var(--accent),transparent);
  background-size:55% 100%;background-position:-55% 0;
}
.wire-down>i{
  background-image:linear-gradient(180deg,transparent,var(--accent),transparent);
  background-size:100% 60%;background-position:0 -60%;
}
@media (prefers-reduced-motion:no-preference){
  .wire-call code,.wire-key,.wire-eng li{
    animation:wire-lit var(--dur) var(--ease) var(--d,0s) infinite;
  }
  .wire-r>i{animation:wire-run var(--dur) linear var(--d,0s) infinite}
  .wire-down>i{animation:wire-run-v var(--dur) linear var(--d,0s) infinite}
  /* backwards, so a result is not on screen before the pulse that produced it.
     Each one holds for the rest of its own cycle and lands again as the pulse
     comes round, which is why there is no seam and no empty strip. */
  .wire-t{animation:wire-pop var(--dur) var(--ease) var(--d,0s) infinite backwards}
  /* Two frames out of the same request, changing over slowly underneath the
     sweep: a set is more than one picture. */
  .wire-t>.wire-v{animation:wire-var calc(var(--dur) * 2) var(--ease-in-out) infinite}
}
@keyframes wire-lit{
  0%{box-shadow:none;transform:translateY(0)}
  4%{box-shadow:0 0 0 3px rgba(255,46,126,.14),inset 0 0 0 1px var(--accent);transform:translateY(-2px)}
  13%{box-shadow:none;transform:translateY(0)}
  100%{box-shadow:none;transform:translateY(0)}
}
@keyframes wire-run{
  0%{background-position:-55% 0;opacity:0}
  1%{opacity:1}
  11%{background-position:155% 0;opacity:1}
  12%{background-position:155% 0;opacity:0}
  100%{background-position:-55% 0;opacity:0}
}
@keyframes wire-run-v{
  0%{background-position:0 -60%;opacity:0}
  1%{opacity:1}
  9%{background-position:0 160%;opacity:1}
  10%{background-position:0 160%;opacity:0}
  100%{background-position:0 -60%;opacity:0}
}
@keyframes wire-pop{
  0%{opacity:0;transform:translateY(10px) scale(.94);box-shadow:0 0 0 0 rgba(255,46,126,0)}
  3%{opacity:1;transform:none;box-shadow:0 0 0 3px rgba(255,46,126,.4)}
  10%{box-shadow:0 0 0 0 rgba(255,46,126,0)}
  100%{opacity:1;transform:none;box-shadow:0 0 0 0 rgba(255,46,126,0)}
}
@keyframes wire-var{
  0%,36%{opacity:0}
  47%,86%{opacity:1}
  100%{opacity:0}
}
/* Stacked, and the rails become the short line between one beat and the next.
   No sweep on them there: a 2px vertical line is too small to read a pulse
   travelling down it, and the chips still light in order. */
@media (max-width:899px){
  .wire{grid-template-columns:1fr;gap:14px}
  .wire-r{width:2px;height:22px;justify-self:center}
  .wire-r>i{display:none}
  .wire-mid,.wire-out{text-align:left}
  .wire-key{display:inline-block}
  .wire-out{margin-top:0}
  .wire-down{display:none}
  .wire-out .wire-l{margin-top:0}
  .wire-strip{justify-content:flex-start}
}

/* --- the blog index: header, filters, search, grid, pages ---------------
   Fifteen articles is the point at which a river becomes a pile. The three
   things that fix it -- a search, the categories, and pages -- are all script
   deep, so every article is in the page and only which ones are shown changes:
   a crawler sees the whole list, and so does a reader with no script, who gets
   no controls rather than controls that do nothing. */
.bloghd{
  display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;
  gap:clamp(16px,2.4vw,34px);margin-top:14px;
}
.bloghd-t{max-width:62ch}
.bloghd-t .lede{margin-top:10px}
.blogsearch{
  position:relative;display:flex;align-items:center;gap:8px;flex:0 1 320px;
  padding:11px 14px;border-radius:var(--r-pill);
  border:1px solid var(--line);background:var(--panel);
  transition:border-color .2s ease,box-shadow .2s ease;
}
.blogsearch:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px rgba(255,46,126,.12)}
.blogsearch>.ico{width:17px;height:17px;color:var(--fg-3);flex:0 0 auto}
.blogsearch input{
  flex:1 1 auto;min-width:0;border:0;background:none;padding:0;
  font-family:var(--sans);font-size:.9375rem;color:var(--fg);
}
.blogsearch input:focus{outline:none}
.blogsearch input::placeholder{color:var(--fg-3)}
.blogsearch input::-webkit-search-cancel-button{display:none}
.blogsearch-x{
  display:grid;place-items:center;width:22px;height:22px;padding:0;flex:0 0 auto;
  border:0;border-radius:50%;background:var(--panel-2);color:var(--fg-2);cursor:pointer;
}
.blogsearch-x:hover{background:var(--line);color:var(--fg)}
.blogsearch-x .ico{width:13px;height:13px}
.bchips{display:flex;flex-wrap:wrap;gap:8px;margin-top:clamp(18px,2.2vw,28px)}
.bchip{
  display:inline-flex;align-items:center;gap:7px;padding:8px 14px;
  border-radius:var(--r-pill);border:1px solid var(--line);background:var(--panel);
  font-family:var(--sans);font-size:.875rem;font-weight:500;color:var(--fg-2);cursor:pointer;
  transition:border-color .2s ease,color .2s ease,background-color .2s ease;
}
.bchip>span{
  font-family:var(--mono);font-size:.6875rem;color:var(--fg-3);
  padding:1px 5px;border-radius:5px;background:var(--panel-2);
}
.bchip:hover{border-color:var(--line-2);color:var(--fg)}
.bchip.is-on{background:var(--fg);border-color:var(--fg);color:var(--bg)}
.bchip.is-on>span{background:rgba(255,255,255,.16);color:rgba(255,255,255,.85)}
/* A real grid, not a flex row: the pages have to keep their shape as cards are
   taken out of them, and a flex row grows the last card of a short page into a
   slab. */
.bgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(288px,1fr));gap:clamp(14px,1.6vw,22px)}
.bgrid>.mcard{max-width:none;flex:none}
/* Load-bearing, and the house pattern (see .tf-s[hidden] and .cc[hidden]):
   display:grid set by a class outranks the browser's own [hidden]{display:none},
   which is a class beating a UA rule. Without this line the pager puts a card
   away and the card stays on the page -- measured, not guessed: four cards
   carried the attribute and all four were still displayed. */
.mcard[hidden],.mlead[hidden],.bgrid[hidden],.blogsearch-x[hidden]{display:none}
.bempty{margin-top:28px;font-size:1.0625rem;color:var(--fg-2)}
.blink{
  border:0;background:none;padding:0;font:inherit;color:var(--accent-fg);
  cursor:pointer;text-decoration:underline;text-underline-offset:3px;
}
.bpager{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-top:clamp(26px,3.2vw,42px)}
.bpage{
  display:grid;place-items:center;min-width:40px;height:40px;padding:0 12px;
  border-radius:var(--r-chip);border:1px solid var(--line);background:var(--panel);
  font-family:var(--sans);font-size:.9375rem;color:var(--fg-2);cursor:pointer;
  transition:border-color .2s ease,color .2s ease,background-color .2s ease;
}
.bpage:hover:not(:disabled){border-color:var(--line-2);color:var(--fg)}
.bpage[aria-current="page"]{background:var(--fg);border-color:var(--fg);color:var(--bg)}
.bpage:disabled{opacity:.4;cursor:default}
.bpage .ico{width:16px;height:16px}
.mk-c{color:var(--accent-fg)}
.mlead.has-m .mk-c{color:#fff}
@media (max-width:639px){
  .blogsearch{flex:1 1 100%}
  .bgrid{grid-template-columns:1fr}
}
/* No script: no controls, and every article on the page. */
.no-js :is(.blogsearch,.bchips,.bpager,.bempty){display:none}

/* --- an article, opening in its own column ------------------------------
   The dark band came off on 11 September 2026 at request. It put the headline
   on black and the cover beside it inside a fixed 3:2 frame, so every picture
   was cut to a shape it was not made in. The column of metadata stays where it
   was; the title, the standfirst and the picture are now the top of the
   reading column, and the picture keeps its own proportions. */
/* Three areas, not two columns and a wrapper: on a phone the title has to
   come before the metadata -- meeting "Category / Published / Author" before
   knowing what the piece is, which is what a stacked sidebar does, is the one
   thing the old dark band got right. */
.art{grid-template-areas:"side head" "side main"}
.artside{grid-area:side}
.arthd{grid-area:head;min-width:0}
.art>.prose{grid-area:main;min-width:0}
.arthd>.crumb{margin-bottom:clamp(14px,1.8vw,22px)}
.arthd{display:grid;gap:clamp(12px,1.6vw,18px);margin-bottom:clamp(26px,3.4vw,44px)}
.arthd .lede{margin:0;max-width:62ch}
.artcover{margin:clamp(6px,1vw,12px) 0 0}
.artcover img{
  display:block;width:100%;height:auto;border-radius:var(--r-card);
  border:1px solid var(--line);background:var(--panel-2);
}
.artcover .cap{margin-top:10px}
/* The panel at the foot of the side column. It is a link: the advertisement
   has a button drawn inside it. Held at 300 inside a 320 column, so it reads
   as a panel in the column rather than as the column's own edge. */
.artbox{
  display:block;max-width:300px;margin-top:clamp(28px,3.4vw,40px);
  border-radius:var(--r-card);overflow:hidden;
  border:1px solid var(--line);background:var(--panel-2);
  transition:border-color .2s ease,transform .3s var(--ease),box-shadow .3s var(--ease);
}
.artbox:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:var(--shadow)}
.artbox img{display:block;width:100%;height:auto}
@media (max-width:939px){
  .art{grid-template-areas:"head" "side" "main"}
  .artside{padding-bottom:22px;border-bottom:1px solid var(--line)}
  .arthd{margin-bottom:0}
}

/* A CRM row can make this slot portrait -- the ratio is the row's, not the
   page's. Square and landscape fill the column as before; a 9:16 or a 4:5 is
   sized by height instead and centred, because 9:16 across a 535px column is
   950px of film in a section whose other half is five short steps. */
.hiwf>.frame{margin-inline:auto}
.hiwf>.frame-9x16,.hiwf>.frame-4x5{width:auto;height:min(72svh,660px)}
