/* Motion tokens: every state change eases along a bezier over a real duration, nothing snaps. */
:root{--ease-out:cubic-bezier(.12,.85,.2,1);--ease-inout:cubic-bezier(.4,0,.2,1);--dur:.25s}   /* --ease-out follows Framer's spring 500/25/0.1: ~90% in 0.1s, settled by 0.25s */

/* Header variants (Framer): the variant swaps at once and the pill scales from the old width to the new one
   while its content is counter-scaled and clipped (js/header-scroll.js animates a scaleX on the pill). data-show says which variant is up: base | alt. */
.hdr-swap{display:grid;justify-items:center;width:100%}
.hdr-swap>.hv{grid-area:1/1;width:100%}
.hdr-swap>.hv-alt,.hdr-swap[data-show=alt]>.hv-base{visibility:hidden;pointer-events:none}
.hdr-swap[data-show=alt]>.hv-alt{visibility:visible;pointer-events:auto}

/* Scroll-target variants: every variant is stacked, the current one cross-fades in. */
.svs{display:grid;width:100%;min-width:0}
.svs>.sv{grid-area:1/1;display:flex;min-width:0;opacity:0;pointer-events:none}   /* the change itself is animated by js/scroll-variants.js */
.svs>.sv.on,.svs:not(:has(>.on))>.sv:first-child{opacity:1;pointer-events:auto}

/* Component hover states: the default and the hover frame are stacked, the hover one fades in. */
.hsw{display:grid;min-width:0;max-width:100%}
.hsw>div{grid-area:1/1;min-width:0;display:flex;transition:opacity var(--dur) var(--ease-out)}
.hsw>.hs-b{opacity:0;pointer-events:none}
.hsw:hover>.hs-b{opacity:1}
.hsw:hover>.hs-a{opacity:0}

/* Glass tone follows the background (js/header-tone.js): over dark areas the veil darkens and the text goes light. */
.hdr-swap p,.hdr-swap [data-icon],.hdr-swap [data-glass-underlay]{transition:color .18s ease,background-color .18s ease,background .18s ease}
.hdr-swap[data-tone=dark],html[data-header-tone=dark] .hdr-swap:not([data-tone]){
  --text-primary:rgb(255,255,255);--text-secondary:rgba(255,255,255,.72);--text-tertiary:rgba(235,235,235,.3);--text-quaternary:rgba(235,235,235,.18);
  --nav-bg-overlay:rgba(0,0,0,.34);
  --floating-tab-selection:rgba(255,255,255,.12);   /* the active tab's pill: a whiter, translucent fill on dark glass */
  --brand:rgb(92,184,255);   /* the active tab's blue on dark glass */
}
/* Dark rim, as in the Library's Safari bar: faint white inner lines and glow instead of the bright light-mode highlights. */
.hdr-swap [data-name="BG Lead"]{transition:box-shadow .18s ease}
.hdr-swap[data-tone=dark] [data-name="BG Lead"],html[data-header-tone=dark] .hdr-swap:not([data-tone]) [data-name="BG Lead"]{box-shadow:inset 0 1px 0 0 rgba(255,255,255,.16),inset 0 -1px 0 0 rgba(255,255,255,.16),inset 0 2px 8px 0 rgba(255,255,255,.08),inset 0 -2px 8px 0 rgba(255,255,255,.08)!important}

/* Anchor links (Link Anchor): on hover Framer grows the dash (Pointer, 16px) and slides the label 24px to the right while the pill fill and the
   text colour come in, all on a spring (1000/25/0.1, settled in ~0.25s). The two frames are stacked, so the dash grows by scaleX and the label slides
   by translateX in both frames (compositor only). */
.hsw:has(>.hs-b [data-name="Pointer"])>div{transition:opacity .3s cubic-bezier(.2,.9,.25,1)}
.hsw:has(>.hs-b [data-name="Pointer"])>.hs-b [data-name="Pointer"]{transform-origin:0 50%;transform:scaleX(0);transition:transform .3s cubic-bezier(.2,.9,.25,1)}
.hsw:has(>.hs-b [data-name="Pointer"])>.hs-b p{transform:translateX(-24px);transition:transform .3s cubic-bezier(.2,.9,.25,1)}
.hsw:has(>.hs-b [data-name="Pointer"])>.hs-a p{transition:transform .3s cubic-bezier(.2,.9,.25,1)}
.hsw:has(>.hs-b [data-name="Pointer"]):hover>.hs-b [data-name="Pointer"],.hsw:has(>.hs-b [data-name="Pointer"]):hover>.hs-b p{transform:none}
.hsw:has(>.hs-b [data-name="Pointer"]):hover>.hs-a p{transform:translateX(24px)}
@media (prefers-reduced-motion:reduce){.hsw:has(>.hs-b [data-name="Pointer"]) *{transition:none!important}}


/* Project pages: the page colour (--page-bg, the Body fill) is also what shows behind the hero, so fast scrolling, rubber-banding at the
   ends or a frame where the hero lags never flashes the white default background. */
html[data-hero-collapse],html[data-hero-collapse] body{background:var(--page-bg,var(--background))}

/* Image hover frames (Case Study): the normal and the hover frame hold the same picture, so fading one out while the other fades in dips the picture
   to ~75% in the middle and the page colour flashes through. The normal frame stays opaque underneath and only goes once the hover frame is fully in. */
.hsw:has(img)>.hs-a{transition:opacity 0s}
.hsw:has(img):hover>.hs-a{transition:opacity 0s linear .3s}

/* Back button: icon only. Framer's hover frame holds the label (clamped to 40px, with three copies of it), so the label is hidden and the arrow stays centred. */
.hsw:has(>.hs-b [data-name="Back Button"])>.hs-b p{display:none}
.hsw:has(>.hs-b [data-name="Back Button"])>.hs-b [data-name="Back Button"]>div{padding:0!important;gap:0!important}

/* Wide screens: the 1800px max-width column sits in the centre of the page (Framer centres it, the export left it at the edge). */
[data-name="Max Width"]{margin-left:auto;margin-right:auto}

/* Keyboard focus: a visible ring on links (Framer's export has none) */
a:focus-visible,button:focus-visible,[tabindex]:focus-visible{outline:2px solid #3b6cff;outline-offset:3px;border-radius:12px}
[data-tone=dark] a:focus-visible{outline-color:#fff}

/* Type weights: only Regular (400) and Medium (500). Bold / semibold never render. */
b,strong,h1,h2,h3,h4,h5,h6,th{font-weight:500}

/* Comment pins spring open (js/comment-open.js): the second, "open" copy of the image is not used any more, the first one stays put */
.hsw.cm>.hs-b{visibility:hidden;opacity:0!important}   /* kept in the layout so the open width can be measured */
.hsw.cm:hover>.hs-a{opacity:1}
