/* Motion kit — base layer shared by every work. Pages add their own tokens and layout on top. */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;text-size-adjust:100%;overflow-x:hidden}
body{margin:0;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}
img,video,canvas,svg{display:block;max-width:100%}
button,input,textarea,select{font:inherit;color:inherit}
button{cursor:pointer;background:none;border:0;padding:0}
a{color:inherit}:where(a){text-decoration:none}
/* D-19: plain links in running text (an unclassed a[href] inside p, or inside li next to other elements) are underlined,
   so colour is never the only cue (WCAG 1.4.1). A list item that is just a link is a link list (menu, index, cards).
   Links in nav, header and footer, menus, tab lists, role=button/tab/menuitem links and any link with a class (a styled
   component: a button, or a link that draws its own line) keep none. Zero specificity: any page rule on the link wins. */
:where(p a[href]:not([class]),li a[href]:not([class]):not(:only-child)):not(:where(nav a,header a,footer a,[role=navigation] a,[role=menu] a,[role=menubar] a,[role=tablist] a,[role=button],[role=tab],[role=menuitem])){text-decoration:underline;text-underline-offset:.15em}
:focus-visible{outline:2px solid currentColor;outline-offset:3px}
/* scrollbars (user rule 2026-10-11: never the OS default): thin and rounded on the page and on every inner scroller.
   The default thumb is the page text colour at 40 % (kit.js publishes the body colour as --k-scroll-ink; without
   kit.js it is the html colour). Pages tune them from their palette with --k-scroll-thumb and --k-scroll-track; a
   page rule (e.g. scrollbar-width:none on a rail that has buttons) wins over the zero-specificity inner rule.
   Chrome and Safari draw the ::-webkit-scrollbar rules (a rounded thumb, no arrow buttons); the standard properties
   apply only where those pseudo-elements do not exist (Firefox), because in Chrome a standard scrollbar-width or
   scrollbar-color switches the webkit rules off and brings the arrow buttons back. */
@supports not selector(::-webkit-scrollbar){
  html{scrollbar-width:thin;scrollbar-color:var(--k-scroll-thumb,color-mix(in srgb,var(--k-scroll-ink,currentColor) 40%,transparent)) var(--k-scroll-track,transparent)}
  :where(html *){scrollbar-width:thin}
}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:var(--k-scroll-track,transparent)}
::-webkit-scrollbar-thumb{background:var(--k-scroll-thumb,color-mix(in srgb,var(--k-scroll-ink,currentColor) 40%,transparent));background-clip:padding-box;border:2px solid transparent;border-radius:999px}
::-webkit-scrollbar-button{display:none}
::-webkit-scrollbar-corner{background:transparent}

/* full-bleed layers */
.k-fill{position:absolute;inset:0;width:100%;height:100%}
.k-fixed{position:fixed;inset:0;width:100%;height:100%}
canvas.k-gl{display:block}

/* ---------- reveal system (kit.reveal) ---------- */
/* Hidden states never transition (so entering them is instant, even if the page painted before the kit ran);
   transitions apply only when .k-in is added. Content is hidden from first paint where scripting is enabled; with
   JavaScript off it shows at once; if the kit never runs, a failsafe animation shows it after 2.5s. */
:root{--ease-out:cubic-bezier(.16,1,.3,1);--ease-in-out:cubic-bezier(.65,0,.35,1);--ease-back:cubic-bezier(.34,1.56,.64,1);--ease-expo:cubic-bezier(.19,1,.22,1)}
/* motion tokens: exit/premium/travel curves, two duration tiers, stagger */
:root{--ease-in:cubic-bezier(.7,0,.84,0);--ease-glide:cubic-bezier(.4,0,.2,1);--ease-travel:cubic-bezier(.83,0,.17,1);--ease-whip:cubic-bezier(.9,0,.1,1);--ease-land:cubic-bezier(.55,0,.2,1.12);--dur-micro:160ms;--dur-small:320ms;--dur-panel:560ms;--dur-hover-in:140ms;--dur-hover-out:220ms;--dur-hero:1100ms;--dur-scene:1500ms;--dur-camera:2400ms;--stagger-word:60ms;--stagger-list:60ms;--stagger-grid:30ms}
/* press, loop tier, text cadence and loop phase stagger (loops run linear outside; easing lives on the keyframes) */
:root{--dur-press:70ms;--dur-loop-tick:1000ms;--dur-loop:2400ms;--dur-loop-drift:6000ms;--dur-loop-slow:12000ms;--cad-type:55ms;--cad-scramble:35ms;--stagger-loop:160ms}
/* stepped curve (carets, braille, e-ink) and the expanding-ring ping */
:root{--ease-steps:steps(1,end);--ease-ping:cubic-bezier(0,.4,.2,1)}
/* registered colours: transitions and gradients that use them interpolate (mix them in oklab: color-mix(in oklab, …)) */
@property --k-color-a{syntax:'<color>';inherits:true;initial-value:transparent}
@property --k-color-b{syntax:'<color>';inherits:true;initial-value:transparent}
@property --k-tint{syntax:'<color>';inherits:true;initial-value:transparent}
@property --k-mix{syntax:'<number>';inherits:true;initial-value:0}
[data-reveal]{--dur:1100ms;--delay:0ms;--stagger:40ms;--dist:28px}
@keyframes k-failsafe{to{opacity:1;transform:none;filter:none;clip-path:none}}
/* D-6: the start state is hidden only where scripting is available; with JavaScript off (or a browser without the
   media feature) the content shows at once. With scripting on, the 2.5 s failsafe still covers a kit that never runs. */
@media (scripting:enabled){
html:not(.k-ready) [data-reveal]{opacity:0;animation:k-failsafe .01s 2.5s forwards}
html:not(.k-ready) [data-reveal=chars],html:not(.k-ready) [data-reveal=words],html:not(.k-ready) [data-reveal=lines]{opacity:0}
html:not(.k-ready) [data-reveal=lines-mask]{opacity:0}
}
.k-ready [data-reveal]:not(.k-in){opacity:0;transition:none}
.k-ready [data-reveal].k-in{transition:opacity var(--dur) var(--ease-out) var(--delay),transform var(--dur) var(--ease-out) var(--delay),filter var(--dur) var(--ease-out) var(--delay),clip-path var(--dur) var(--ease-expo) var(--delay)}
.k-ready [data-reveal=up]:not(.k-in){transform:translateY(var(--dist))}
.k-ready [data-reveal=down]:not(.k-in){transform:translateY(calc(var(--dist) * -1))}
.k-ready [data-reveal=left]:not(.k-in){transform:translateX(calc(var(--dist) * 1.4))}
.k-ready [data-reveal=right]:not(.k-in){transform:translateX(calc(var(--dist) * -1.4))}
.k-ready [data-reveal=blur]:not(.k-in){filter:blur(14px);transform:translateY(12px) scale(.98)}
.k-ready [data-reveal=scale]:not(.k-in){transform:scale(.92)}
.k-ready [data-reveal=clip]:not(.k-in){clip-path:inset(0 100% 0 0);opacity:1}
.k-ready [data-reveal=clip-up]:not(.k-in){clip-path:inset(100% 0 0 0);opacity:1}
.k-ready [data-reveal=mask]:not(.k-in){clip-path:inset(0 0 100% 0);transform:translateY(30%);opacity:1}
.k-ready [data-reveal=clip].k-in,.k-ready [data-reveal=clip-up].k-in,.k-ready [data-reveal=mask].k-in{clip-path:inset(0 0 0 0)} /* explicit end state so inset() interpolates instead of snapping to none */
/* split text: the container stays visible, pieces animate */
.k-ready [data-reveal=chars],.k-ready [data-reveal=words],.k-ready [data-reveal=lines]{opacity:1!important;transition:none!important}
.k-word{display:inline-block;white-space:nowrap}
.k-piece{display:inline-block;will-change:transform}
.k-ready [data-reveal=chars]:not(.k-in) .k-char{opacity:0;transform:translateY(.6em) rotate(4deg);filter:blur(6px);transition:none}
.k-ready [data-reveal=words]:not(.k-in) .k-word{opacity:0;transform:translateY(.5em);filter:blur(8px);transition:none}
.k-ready [data-reveal=lines]:not(.k-in) .k-word{transform:translateY(.75em);opacity:0;clip-path:inset(0 0 100% 0);transition:none}
.k-ready [data-reveal=chars].k-in .k-char,.k-ready [data-reveal=words].k-in .k-word{transition:opacity var(--dur) var(--ease-out),transform var(--dur) var(--ease-out),filter var(--dur) var(--ease-out);transition-delay:calc(var(--delay) + var(--i) * var(--stagger))}
.k-ready [data-reveal=lines].k-in .k-word{transition:transform var(--dur) var(--ease-expo),opacity var(--dur) var(--ease-out),clip-path var(--dur) var(--ease-expo);transition-delay:calc(var(--delay) + var(--i) * var(--stagger) * var(--line-k,3))}
/* lines step by --stagger × --line-k (default 3, unchanged for existing pages; the site engine sets 1 and gives the
   hero headline a stagger computed from its line count so the whole cascade lands within 1.2 s) */
/* lines-mask: a true masked line reveal. Each word is a static mask padded for ascenders, italic overhang and descenders
   (inset -.12em -.08em -.22em); its text (.k-wi) rises from below the mask edge with a small tilt that levels out. */
.k-ready [data-reveal=lines-mask]{opacity:1!important;transition:none!important}
[data-reveal=lines-mask] .k-word{clip-path:inset(-.12em -.08em -.22em -.08em);will-change:auto}
[data-reveal=lines-mask] .k-wi{display:inline-block;transform-origin:0 100%}
.k-ready [data-reveal=lines-mask]:not(.k-in) .k-wi{transform:translateY(calc(100% + .3em)) rotate(4deg);transition:none}
.k-ready [data-reveal=lines-mask].k-in .k-wi{transition:transform var(--dur) var(--ease-expo);transition-delay:calc(var(--delay) + var(--i) * var(--stagger) * var(--line-k,3))}
/* choreography by rank (opt-in data-rank): the boss travels furthest and leads; secondary and tertiary follow (data-delay wins) */
[data-reveal][data-rank="1"]{--dist:48px}
[data-reveal][data-rank="2"]{--dist:24px;--delay:400ms}
[data-reveal][data-rank="3"]{--dist:12px;--delay:700ms}
/* content the visitor has already passed (anchor jump, reload or back mid-page) shows its final state at once */
.k-instant,.k-instant *{transition:none!important}

/* ---------- utilities ---------- */
.k-grain{position:fixed;inset:0;width:100%!important;height:100%!important;pointer-events:none;z-index:9999;background-repeat:repeat}
.k-cursor{position:fixed;left:0;top:0;border-radius:50%;pointer-events:none;z-index:10000;transition:opacity .3s}
.k-glass{background:rgba(255,255,255,.08);backdrop-filter:blur(18px) saturate(140%);-webkit-backdrop-filter:blur(18px) saturate(140%);border:1px solid rgba(255,255,255,.14)}
.k-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* scramble() reserved width (K03): a one-token inline target becomes inline-block so its min-inline-size (in ch) applies */
:where(.k-scr-box:not([hidden])){display:inline-block}
.k-nogl{background:radial-gradient(120% 80% at 50% 0%,#334 0%,#000 70%)}

/* ---------- motion recipes (opt-in classes; transform/opacity/clip only) ---------- */
/* keycap / push edge: the face drops onto its edge in --dur-press, comes back on a landing curve */
.k-press-edge{box-shadow:0 var(--edge,4px) 0 0 var(--edge-color,rgba(0,0,0,.35));transition:transform var(--dur-hover-out) var(--ease-land),box-shadow var(--dur-hover-out) var(--ease-land)}
.k-press-edge:active{transform:translateY(var(--edge,4px));box-shadow:0 0 0 0 var(--edge-color,rgba(0,0,0,.35));transition-duration:var(--dur-press)}
.k-lift{transition:transform var(--dur-hover-out) var(--ease-out),box-shadow var(--dur-hover-out) var(--ease-out)}
.k-lift:hover,.k-lift:focus-visible{transform:translateY(-10px);box-shadow:0 18px 40px -18px rgba(0,0,0,.45);transition-duration:var(--dur-hover-in)}
/* hit-area keeper (D-14): while hovered, an unpainted ::after strip fills the 10 px the lift vacates, so a pointer
   resting near the bottom edge keeps :hover instead of flickering. .k-lift is position:relative and owns ::after. */
:where(.k-lift){position:relative}
.k-lift:hover::after{content:"";position:absolute;left:0;right:0;top:100%;height:10px}
/* underline that draws in from the left and leaves to the right (origin swap) */
.k-underline{position:relative}
.k-underline::after{content:'';position:absolute;left:0;right:0;bottom:-.12em;height:max(1px,.06em);background:currentColor;transform:scaleX(0);transform-origin:100% 50%;transition:transform var(--dur-small) var(--ease-out)}
.k-underline:hover::after,.k-underline:focus-visible::after{transform:scaleX(1);transform-origin:0 50%}
/* expand on hover / focus-within: laid out at full width, clipped to --collapsed (no width animation) */
.k-expand{clip-path:inset(0 calc(100% - var(--collapsed,2.75em)) 0 0 round 999px);transition:clip-path var(--dur-panel) var(--ease-expo)}
.k-expand:hover,.k-expand:focus-within{clip-path:inset(0 0 0 0 round 999px)}
/* dock neighbours: hovering one child dims and shrinks its siblings */
.k-has-dim>*{transition:opacity var(--dur-hover-out) var(--ease-out),scale var(--dur-hover-out) var(--ease-out)}
.k-has-dim:has(>:hover)>:not(:hover){opacity:.45;scale:.97}
.k-has-dim:has(:focus-visible)>:not(:focus-within){opacity:.45;scale:.97} /* keyboard twin: the focused child stays */
.k-blob-morph{border-radius:42% 58% 63% 37%/41% 44% 56% 59%;animation:k-blob-morph var(--dur-loop-slow) var(--ease-in-out) infinite alternate}
@keyframes k-blob-morph{50%{border-radius:58% 42% 45% 55%/55% 60% 40% 45%}100%{border-radius:38% 62% 56% 44%/48% 39% 61% 52%}}
/* grid plane that tips back in perspective on hover */
.k-grid-tip{background-image:linear-gradient(var(--grid-line,rgba(127,127,127,.25)) 1px,transparent 1px),linear-gradient(90deg,var(--grid-line,rgba(127,127,127,.25)) 1px,transparent 1px);background-size:var(--grid,32px) var(--grid,32px);transform-origin:50% 100%;transition:transform var(--dur-panel) var(--ease-out)}
.k-grid-tip:hover{transform:perspective(900px) rotateX(18deg)}
/* sequence() pause/play control (opt-in `controls`) */
.k-seq-pause{position:absolute;right:12px;bottom:12px;z-index:2;width:36px;height:36px;border-radius:50%;display:grid;place-items:center;background:rgba(0,0,0,.5);color:#fff}
.k-seq-pause svg{width:14px;height:14px}

/* mobile motion tier (opt-in <html class="k-tier">): at <= 600 px UI durations x0.8, staggers -30 %, kit parallax off */
@media (max-width:600px){
  html.k-tier{--dur-micro:128ms;--dur-small:256ms;--dur-panel:448ms;--dur-hover-in:112ms;--dur-hover-out:176ms;--stagger-word:42ms;--stagger-list:42ms;--stagger-grid:21ms;--stagger-loop:112ms}
  html.k-tier [data-reveal]{--stagger:28ms;--dist:min(28px,20vw)}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-delay:0s!important;animation-iteration-count:1!important;transition-duration:.001ms!important;transition-delay:0s!important;scroll-behavior:auto!important}
  /* opt-in <html class="k-rm-fade">: positions jump, opacity still fades over --dur-micro */
  html.k-rm-fade.k-ready [data-reveal].k-in,html.k-rm-fade.k-ready [data-reveal].k-in .k-piece{transition-property:opacity!important;transition-duration:var(--dur-micro)!important;transition-delay:0s!important}
}

/* Page-wide motion pause (fx/pause.js; WCAG 2.2.2): html.k-paused rests every CSS loop. Inert until something pauses. */
.k-paused *,.k-paused *::before,.k-paused *::after{animation-play-state:paused!important}
/* motionToggle(): a plain button whose text swaps "Pause motion" / "Play motion" (APG rotation control); inherits the
   page's font and colour; .k-motion--kit is the default look (bare:true opts out); static [data-k-motion] markup is
   laid out from the first paint. Inner spans are shielded from page rules such as `.top span{…}`. */
.k-motion{font:inherit;color:inherit;cursor:pointer;-webkit-tap-highlight-color:transparent}
.k-motion[hidden]{display:none!important}
@media (prefers-reduced-motion:reduce){.k-motion{display:none!important}}
.k-motion--kit{display:inline-flex;align-items:center;gap:.5em;min-height:32px;padding:.3em .85em;border:1px solid color-mix(in srgb,currentColor 40%,transparent);border-radius:999px;background:transparent;font-size:max(13px,.8em);line-height:1.1;letter-spacing:0;white-space:nowrap;transition:background-color var(--dur-hover-out) var(--ease-out),border-color var(--dur-hover-out) var(--ease-out)}
.k-motion--kit:hover{background:color-mix(in srgb,currentColor 10%,transparent);border-color:currentColor;transition-duration:var(--dur-hover-in)}
.k-motion:focus-visible{outline:2px solid currentColor;outline-offset:2px}
button.k-motion>span.k-motion-i,button.k-motion>span.k-motion-l{margin:0;padding:0;border:0;border-radius:0;background:none;box-shadow:none;font:inherit;color:inherit;letter-spacing:inherit;text-transform:none;text-decoration:none}
button.k-motion>span.k-motion-i{position:relative;display:inline-block;width:.6em;height:.7em;flex:none}
button.k-motion>span.k-motion-i::before,button.k-motion>span.k-motion-i::after{content:"";position:absolute;top:0;bottom:0;width:32%;background:currentColor;border-radius:1px}
button.k-motion>span.k-motion-i::before{left:0}button.k-motion>span.k-motion-i::after{right:0}
button.k-motion.is-paused>span.k-motion-i::before{width:0;height:0;left:10%;bottom:auto;background:none;border-radius:0;border-style:solid;border-width:.35em 0 .35em .6em;border-color:transparent transparent transparent currentColor}
button.k-motion.is-paused>span.k-motion-i::after{display:none}
/* forced colours repaint borders and backgrounds, which would turn the glyph into a box (play) or erase it (pause bars):
   there the glyph opts out of the repaint and is drawn in the system button text colour */
@media (forced-colors:active){
  button.k-motion>span.k-motion-i::before,button.k-motion>span.k-motion-i::after{forced-color-adjust:none;background:ButtonText}
  button.k-motion.is-paused>span.k-motion-i::before{background:none;border-color:transparent transparent transparent ButtonText}
}
button.k-motion>span.k-motion-l{display:inline-grid}
button.k-motion>span.k-motion-l::after{content:attr(data-alt);height:0;overflow:hidden;visibility:hidden} /* width of the longer label: the swap never moves the neighbours */
.k-motion--compact{justify-content:center;min-width:32px;padding-inline:0}
.k-motion--compact .k-motion-l{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.k-motion--float{position:fixed;right:max(16px,env(safe-area-inset-right));bottom:max(16px,env(safe-area-inset-bottom));z-index:70;color:var(--k-motion-fg,#fff);background:var(--k-motion-bg,rgba(12,12,14,.62));-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}
.k-motion--float:hover{background:var(--k-motion-bg,rgba(12,12,14,.78))}
@media (max-width:560px){.k-motion--kit:not(.k-motion--label) .k-motion-l{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}.k-motion--kit:not(.k-motion--label){justify-content:center;min-width:32px;padding-inline:0}}
.k-motion-w{display:flex;align-items:center;margin-block:-8px} /* wrap:'li' — the pill never stretches a text row */
