/* Offerings "What we built" visuals, one block per point. Resting styles are the finished picture
   (what reduced motion shows); each loop only runs while its panel is .is-active. */

@property --px { syntax: "<number>"; inherits: true; initial-value: 0.5; }

.viz { --enter: cubic-bezier(0.22, 1, 0.36, 1); --pop: cubic-bezier(0.34, 1.56, 0.64, 1); }
/* scale and rotate around each mark's own centre; elements placed with a transform attribute keep its origin */
:where(.viz) :where(g, rect, circle, path, ellipse):not([transform]) { transform-box: fill-box; transform-origin: center; }
@keyframes v-pop { from { opacity: 0; scale: 0.4; } }
@keyframes v-fade { from { opacity: 0; } }
@keyframes v-rise { from { opacity: 0; translate: 0 8px; } }
@keyframes v-draw { from { stroke-dashoffset: 100; } }
@keyframes v-grow-x { from { scale: 0 1; } }
@keyframes v-spin { to { rotate: 360deg; } }
/* a mark that pops in once per loop, holds, then clears before the loop comes round again */
@keyframes v-blip { 0% { opacity: 0; scale: 0.5; } 4% { opacity: 1; scale: 1; } 84% { opacity: 1; scale: 1; } 94%, 100% { opacity: 0; scale: 1; } }

/* scrubbed visuals idle on their own until the pointer takes over (offerings.js adds .is-scrubbing) */
.viz--denoise.is-active:not(.is-scrubbing) { animation: dn-idle 6s var(--ease-in-out) infinite alternate; }
@keyframes dn-idle { from { --px: 0.24; } to { --px: 0.76; } }
.viz.is-scrubbing { cursor: ew-resize; }

/* ---- Koning 01 · denoising: noisy low-dose input on the left of the handle, denoised on the right ---- */
.dn__layer { position: absolute; inset: 0; width: 100%; height: 100%; }
.dn__layer--noisy { clip-path: inset(0 calc((1 - var(--px)) * 100%) 0 0); }
.dn__grain { opacity: 0.6; }
.dn__label { position: absolute; left: 0; top: 0; font-size: var(--text-xs); color: color-mix(in oklab, var(--white) 60%, transparent); }
.dn__label--r { left: auto; right: 0; color: var(--navy-300); }
.dn__handle { position: absolute; top: 1.5rem; bottom: 0; left: calc(var(--px) * 100%); width: 1px; background: color-mix(in oklab, var(--white) 65%, transparent); }
.dn__handle i {
  position: absolute; top: 50%; left: 50%; width: 30px; height: 30px; margin: -15px 0 0 -15px; border-radius: 50%;
  background: var(--white); box-shadow: 0 6px 18px color-mix(in oklab, var(--black) 35%, transparent);
}
.dn__handle i::before, .dn__handle i::after { /* ‹ › chevrons */
  content: ""; position: absolute; top: 50%; width: 5px; height: 5px; margin-top: -3px;
  border: solid var(--navy); border-width: 0 0 1.5px 1.5px;
}
.dn__handle i::before { left: 8px; rotate: 45deg; }
.dn__handle i::after { right: 8px; rotate: -135deg; }

/* ---- Koning 02 · detection: a pass sweeps the slice; each finding is ringed and measured as it is reached ---- */
.det__slice { opacity: 0.9; }
.det__ring { fill: none; stroke: var(--navy-300); stroke-width: 1.5; }
.det__lead { fill: none; stroke: color-mix(in oklab, var(--white) 40%, transparent); }
.det__end { fill: var(--navy-300); }
.det__beam { fill: url(#of-beam); }
.det__line { stroke: var(--navy-300); stroke-width: 1.5; }
.det__scan { opacity: 0; translate: 0 52px; transform-box: view-box; }
.viz--detect.is-active .det__scan { opacity: 1; animation: det-scan 4s linear infinite; }
@keyframes det-scan { from { translate: 0 52px; } to { translate: 0 262px; } }
.viz--detect.is-active .det__hit { animation: v-blip 4s linear var(--d) infinite both; }

/* ---- Koning 03 · simulation: the source and detector orbit the phantom while the sinogram fills ---- */
.sim__orbit { fill: none; stroke: color-mix(in oklab, var(--white) 14%, transparent); stroke-dasharray: 2 6; }
.sim__phantom circle:first-child { fill: color-mix(in oklab, var(--white) 6%, transparent); stroke: color-mix(in oklab, var(--white) 40%, transparent); }
.sim__phantom path { fill: color-mix(in oklab, var(--navy-300) 25%, transparent); stroke: color-mix(in oklab, var(--navy-300) 65%, transparent); }
.sim__gantry { transform-box: view-box; transform-origin: 200px 150px; rotate: 35deg; }
.sim__fan { fill: color-mix(in oklab, var(--navy-300) 10%, transparent); }
.sim__rays path { fill: none; stroke: color-mix(in oklab, var(--navy-300) 45%, transparent); stroke-width: 0.8; stroke-dasharray: 3 5; }
.sim__det { fill: none; stroke: color-mix(in oklab, var(--white) 75%, transparent); stroke-width: 6; stroke-linecap: round; }
.sim__src { fill: var(--white); }
.sim__src-glow { fill: url(#of-heat); }
.sim__panel { fill: color-mix(in oklab, var(--white) 4%, transparent); stroke: color-mix(in oklab, var(--white) 12%, transparent); }
.sim__curve { fill: none; stroke: var(--navy-300); stroke-width: 1.4; stroke-dasharray: 100; }
.sim__curve:nth-of-type(2) { stroke: var(--white); opacity: 0.6; }
.viz--sim.is-active .sim__gantry { animation: sim-orbit 9s linear infinite; }
@keyframes sim-orbit { from { rotate: 0deg; } to { rotate: 360deg; } }
.viz--sim.is-active .sim__rays path { animation: sim-flow 0.6s linear infinite; }
@keyframes sim-flow { to { stroke-dashoffset: -8; } }
.viz--sim.is-active .sim__curve { animation: v-draw 9s linear infinite; }

/* ---- Koning 04 · registration: the new scan (blue) settles onto the reference and the error drops ---- */
.reg__ref { opacity: 0.45; }
.reg__mark { fill: none; stroke: color-mix(in oklab, var(--white) 55%, transparent); stroke-width: 1.2; }
.reg__mov { transform-box: view-box; transform-origin: 240px 160px; }
.viz--register.is-active .reg__mov { animation: reg-align 6s var(--ease-in-out) infinite; }
@keyframes reg-align {
  0%, 12% { translate: 34px -20px; rotate: -9deg; scale: 1.05; }
  45%, 84% { translate: 0 0; rotate: 0deg; scale: 1; }
  100% { translate: 34px -20px; rotate: -9deg; scale: 1.05; }
}
.viz--register.is-active .reg__readout b:first-child { animation: reg-before 6s linear infinite; }
.viz--register.is-active .reg__readout b + b { animation: reg-after 6s linear infinite; }
@keyframes reg-before { 0%, 38% { opacity: 1; } 42%, 88% { opacity: 0; } 92%, 100% { opacity: 1; } }
@keyframes reg-after { 0%, 38% { opacity: 0; } 42%, 88% { opacity: 1; } 92%, 100% { opacity: 0; } }

/* ---- Koning 05 · reconstruction: cores light in waves; the same job finishes in a quarter of the time ---- */
.rc__cores rect { fill: color-mix(in oklab, var(--navy-300) 55%, transparent); }
.rc__track { fill: color-mix(in oklab, var(--white) 8%, transparent); }
.rc__bar { transform-origin: left; }
.rc__bar--before { fill: color-mix(in oklab, var(--white) 45%, transparent); }
.rc__bar--after { fill: var(--navy-300); }
.viz--recon.is-active .rc__cores rect { animation: rc-core 1.1s var(--ease-in-out) calc(var(--k) * -0.09s) infinite alternate; }
@keyframes rc-core { from { fill: color-mix(in oklab, var(--white) 8%, transparent); } to { fill: var(--navy-300); } }
.viz--recon.is-active .rc__bar--before { animation: rc-before 4.5s linear infinite; }
.viz--recon.is-active .rc__bar--after { animation: rc-after 4.5s linear infinite; }
@keyframes rc-before { from { scale: 0 1; } to { scale: 1 1; } }
@keyframes rc-after { 0% { scale: 0 1; } 26%, 100% { scale: 1 1; } }

/* ---- Bitwise 01 · elastic inference: workers switch on as the upload peak passes, then stand down ---- */
.el__grid { fill: none; stroke: color-mix(in oklab, var(--white) 7%, transparent); }
.el__area { fill: color-mix(in oklab, var(--navy-300) 18%, transparent); }
.el__line { fill: none; stroke: var(--navy-300); stroke-width: 2; }
.el__cursor { translate: 260px 0; transform-box: view-box; }
.el__cursor path { stroke: color-mix(in oklab, var(--white) 55%, transparent); stroke-dasharray: 3 4; }
.el__vm rect { fill: var(--navy-300); stroke: color-mix(in oklab, var(--white) 18%, transparent); }
.el__vm path { fill: none; stroke: color-mix(in oklab, var(--ink) 55%, transparent); stroke-width: 2; stroke-linecap: round; }
.viz--elastic.is-active .el__cursor { animation: el-sweep 8s linear infinite; }
@keyframes el-sweep { from { translate: 0 0; } to { translate: 400px 0; } }
.viz--elastic.is-active .el__vm--b rect { animation: vm-b 8s linear infinite; }
.viz--elastic.is-active .el__vm--c rect { animation: vm-c 8s linear infinite; }
.viz--elastic.is-active .el__vm--d rect { animation: vm-d 8s linear infinite; }
@keyframes vm-b { 0%, 32% { fill: color-mix(in oklab, var(--white) 8%, transparent); } 35%, 88% { fill: var(--navy-300); } 91%, 100% { fill: color-mix(in oklab, var(--white) 8%, transparent); } }
@keyframes vm-c { 0%, 44% { fill: color-mix(in oklab, var(--white) 8%, transparent); } 47%, 80% { fill: var(--navy-300); } 83%, 100% { fill: color-mix(in oklab, var(--white) 8%, transparent); } }
@keyframes vm-d { 0%, 54% { fill: color-mix(in oklab, var(--white) 8%, transparent); } 57%, 72% { fill: var(--navy-300); } 75%, 100% { fill: color-mix(in oklab, var(--white) 8%, transparent); } }

/* ---- Bitwise 02 · fruit counting: the camera pass boxes every fruit and totals each tree ---- */
.fr__ground { stroke: color-mix(in oklab, var(--white) 22%, transparent); }
/* solid trees: one opaque canopy (overlapping lobes share a page-space gradient, so no seams) plus a soft shade */
.fr__shadow { fill: color-mix(in oklab, var(--black) 45%, transparent); filter: blur(3px); }
.fr__trunk { fill: color-mix(in oklab, var(--slate-300) 60%, var(--navy-700)); }
.fr__canopy circle { fill: url(#of-tree); }
.fr__shade { mix-blend-mode: multiply; }
.fr__shade circle { fill: url(#of-tree-light); }
.fr__fruit { fill: var(--white); }
.fr__box { fill: none; stroke: var(--white); stroke-width: 1.2; }
.fr__count path { stroke: color-mix(in oklab, var(--white) 40%, transparent); }
.fr__count rect { fill: var(--white); }
.fr__count text { font-size: 10px; font-weight: 700; fill: var(--ink); }
.fr__band { fill: url(#of-band); }
.fr__scan { opacity: 0; transform-box: view-box; }
.fr__scan path { stroke: var(--navy-300); stroke-width: 1.5; }
.viz--fruit.is-active .fr__scan { opacity: 1; animation: fr-scan 7s linear infinite; }
@keyframes fr-scan { from { translate: 40px 0; } to { translate: 440px 0; } }
.viz--fruit.is-active :is(.fr__box, .fr__count) { animation: v-blip 7s var(--enter) var(--d) infinite both; }

/* ---- Bitwise 03 · monitored results: yield by block, the weak block flagged (blocks respond to hover) ---- */
.map__block { fill: var(--navy-300); fill-opacity: var(--v); stroke: transparent; stroke-width: 1.5; transition: fill-opacity 200ms, stroke 200ms; }
.map__block:hover { fill-opacity: 1; stroke: var(--white); }
.map__block--low { fill-opacity: 0.12; stroke: color-mix(in oklab, var(--white) 60%, transparent); stroke-dasharray: 3 3; }
.map__pulse { fill: none; stroke: var(--white); stroke-width: 1.5; opacity: 0; }
.map__flag path { stroke: color-mix(in oklab, var(--white) 60%, transparent); }
.map__flag rect { fill: var(--white); }
.map__flag text { font-size: 9.5px; font-weight: 600; fill: var(--ink); }
.map__val { font-size: 15px; font-weight: 600; fill: var(--white); letter-spacing: -0.01em; }
.map__track { fill: color-mix(in oklab, var(--white) 10%, transparent); }
.map__fill { fill: var(--navy-300); transform-origin: left; scale: var(--w) 1; }
.map__legend { fill: url(#of-legend); }
.viz--map.is-active .map__block { animation: v-fade 400ms var(--enter) calc(var(--k) * 30ms) both; }
.viz--map.is-active .map__pulse { animation: map-pulse 2s var(--ease-out) 0.9s infinite; }
@keyframes map-pulse { from { opacity: 0.9; scale: 0.6; } to { opacity: 0; scale: 1.8; } }
.viz--map.is-active .map__flag { animation: v-rise 450ms var(--enter) 0.8s both; }
.viz--map.is-active .map__fill { animation: map-fill 700ms var(--enter) 0.3s both; }
@keyframes map-fill { from { scale: 0 1; } }

/* ---- Bitwise 04 · CI/CD: a candidate passes every gate, then replaces the live model with no gap ---- */
.ci__rail { fill: none; stroke: color-mix(in oklab, var(--white) 15%, transparent); stroke-width: 2; }
.ci__rail--lit { stroke: var(--navy-300); stroke-dasharray: 100; }
.ci__node circle:first-child { fill: var(--ink-800); stroke: color-mix(in oklab, var(--white) 28%, transparent); }
.ci__core { fill: var(--navy-300); }
.ci__token { fill: var(--white); transform-box: view-box; opacity: 0; }
.ci__card { fill: color-mix(in oklab, var(--white) 5%, transparent); stroke: color-mix(in oklab, var(--white) 15%, transparent); }
.ci__v { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; fill: var(--white); }
.ci__v--old { opacity: 0; }
.ci__uptime { fill: none; stroke: var(--navy-300); stroke-width: 2; stroke-dasharray: 4 4; }
.viz--cicd.is-active .ci__rail--lit { animation: ci-rail 5s linear infinite; }
@keyframes ci-rail { 0% { stroke-dashoffset: 100; } 80%, 100% { stroke-dashoffset: 0; } }
.viz--cicd.is-active .ci__token { animation: ci-token 5s linear infinite; }
@keyframes ci-token { 0% { translate: 0 0; opacity: 1; } 80% { translate: 340px 0; opacity: 1; } 90%, 100% { translate: 340px 0; opacity: 0; } }
.viz--cicd.is-active .ci__core { animation: ci-ping 5s var(--ease-out) calc(var(--k) * 1.333s) infinite both; }
@keyframes ci-ping { 0% { fill: var(--white); scale: 1.8; } 12%, 100% { fill: var(--navy-300); scale: 1; } }
.viz--cicd.is-active .ci__v--old { animation: ci-old 5s linear infinite; }
.viz--cicd.is-active .ci__v--new { animation: ci-new 5s var(--enter) infinite; }
@keyframes ci-old { 0%, 80% { opacity: 1; translate: 0 0; } 86%, 100% { opacity: 0; translate: 0 -10px; } }
@keyframes ci-new { 0%, 82% { opacity: 0; translate: 0 10px; } 88%, 100% { opacity: 1; translate: 0 0; } }
.viz--cicd.is-active .ci__uptime { animation: ci-uptime 0.8s linear infinite; }
@keyframes ci-uptime { to { stroke-dashoffset: -8; } }

/* ---- MPay 01 · heatmap corners: four probability blobs, keypoints locked, even under a thumb ---- */
.ht__thumb { fill: color-mix(in oklab, var(--slate-300) 92%, var(--navy)); stroke: color-mix(in oklab, var(--white) 35%, transparent); }
.ht__blob { fill: url(#of-heat); mix-blend-mode: screen; }
.ht__quad { fill: none; stroke: var(--navy-300); stroke-width: 1.5; stroke-dasharray: 100; }
.ht__kp circle { fill: none; stroke: var(--white); stroke-width: 2; }
.ht__kp path { stroke: var(--white); stroke-width: 1.5; }
.viz--heat.is-active .ht__blob { animation: ht-breathe 2.4s var(--ease-in-out) calc(var(--k) * -0.6s) infinite alternate; }
@keyframes ht-breathe { from { opacity: 0.55; scale: 0.82; } to { opacity: 1; scale: 1.08; } }
.viz--heat.is-active .ht__kp { animation: v-pop 450ms var(--pop) calc(0.35s + var(--k) * 120ms) both; }
.viz--heat.is-active .ht__quad { animation: v-draw 800ms var(--enter) 0.9s both; }

/* ---- MPay 02 · orientation: the frame tries all eight, then settles on the upright match ---- */
.or__track { fill: color-mix(in oklab, var(--white) 10%, transparent); }
.or__score { fill: color-mix(in oklab, var(--white) 45%, transparent); transform-origin: left; }
.or__cell:first-of-type .or__score { fill: var(--navy-300); }
.or__frame { fill: none; stroke: var(--navy-300); stroke-width: 2; transform-box: view-box; }
.or__best { fill: color-mix(in oklab, var(--navy-300) 16%, transparent); }
.viz--orient.is-active .or__frame { animation: or-try 5s steps(1, end) infinite; }
@keyframes or-try {
  0% { translate: 0 0; } 9% { translate: 100px 0; } 18% { translate: 200px 0; } 27% { translate: 300px 0; }
  36% { translate: 0 120px; } 45% { translate: 100px 120px; } 54% { translate: 200px 120px; } 63% { translate: 300px 120px; }
  72%, 100% { translate: 0 0; }
}
.viz--orient.is-active .or__best { animation: or-best 5s linear infinite; }
@keyframes or-best { 0%, 71% { opacity: 0; } 74%, 100% { opacity: 1; } }
.viz--orient.is-active .or__score { animation: v-grow-x 600ms var(--enter) calc(var(--k) * 60ms) both; }

/* ---- MPay 03 · perspective: the photographed card flattens into the target frame ---- */
.ps { display: grid; place-items: center; perspective: 900px; container-type: size; }
.ps__target, .ps__card { grid-area: 1 / 1; width: min(62cqw, 105cqh); aspect-ratio: 182 / 120; }
.ps__target { border: 1.5px dashed color-mix(in oklab, var(--white) 35%, transparent); border-radius: 10px; }
.ps__card { filter: drop-shadow(0 18px 24px color-mix(in oklab, var(--black) 40%, transparent)); }
.ps__card svg { width: 100%; height: 100%; overflow: visible; }
.ps__corners circle { fill: var(--navy-300); stroke: var(--white); stroke-width: 1.5; }
.ps__matrix { position: absolute; left: 0; right: 0; bottom: 0; text-align: center; font-size: var(--text-xs); letter-spacing: 0.02em; color: color-mix(in oklab, var(--white) 55%, transparent); font-variant-numeric: tabular-nums; }
.viz--persp.is-active .ps__card { animation: ps-warp 5s var(--ease-in-out) infinite; }
@keyframes ps-warp {
  0%, 14% { transform: translate(-6%, 5%) rotateX(44deg) rotateY(-26deg) rotateZ(-14deg) scale(0.84); }
  48%, 84% { transform: none; }
  100% { transform: translate(-6%, 5%) rotateX(44deg) rotateY(-26deg) rotateZ(-14deg) scale(0.84); }
}

/* ---- MPay 04 · fallback: glare hides a corner; colour segmentation sweeps the card and recovers it ---- */
.fb__mask rect { fill: color-mix(in oklab, var(--navy-300) 32%, transparent); stroke: var(--navy-300); stroke-width: 1.5; transform-origin: left; }
.fb__glare { fill: url(#of-glare); mix-blend-mode: screen; }
.fb__kp { fill: var(--navy-300); stroke: var(--white); stroke-width: 1.5; }
.fb__lost { opacity: 0; }
.fb__lost circle { fill: none; stroke: var(--ink); stroke-width: 1.5; stroke-dasharray: 2 3; }
.fb__lost text { font-size: 11px; font-weight: 700; fill: var(--ink); }
.viz--fallback.is-active .fb__glare { animation: fb-glare 3s var(--ease-in-out) infinite alternate; }
@keyframes fb-glare { from { opacity: 0.75; scale: 0.92; } }
.viz--fallback.is-active .fb__mask rect { animation: fb-mask 6s var(--enter) infinite both; }
@keyframes fb-mask { 0%, 42% { scale: 0 1; opacity: 1; } 62%, 90% { scale: 1 1; opacity: 1; } 98%, 100% { scale: 1 1; opacity: 0; } }
.viz--fallback.is-active .fb__lost { animation: fb-lost 6s linear infinite; }
@keyframes fb-lost { 0%, 60% { opacity: 1; } 63%, 100% { opacity: 0; } }
.viz--fallback.is-active .fb__kp--found { animation: fb-found 6s var(--pop) infinite both; }
@keyframes fb-found { 0%, 62% { opacity: 0; scale: 0.3; } 66%, 92% { opacity: 1; scale: 1; } 98%, 100% { opacity: 0; } }
.viz--fallback.is-active .swap--fb b:first-child { animation: reg-before 6s linear infinite; }
.viz--fallback.is-active .swap--fb b + b { animation: reg-after 6s linear infinite; }

/* ---- MPay 05 · OCR: each field is boxed on the card and lands, structured, in the list ---- */
.oc__box { fill: color-mix(in oklab, var(--navy-300) 16%, transparent); stroke: var(--navy-300); stroke-width: 1.2; }
.oc__div { stroke: color-mix(in oklab, var(--white) 12%, transparent); }
.oc__val { font-size: 12px; font-weight: 500; fill: var(--white); }
.viz--ocr.is-active .oc__box { animation: v-blip 7s var(--enter) calc(var(--k) * 0.55s) infinite both; }
.viz--ocr.is-active .oc__row { animation: oc-row 7s var(--enter) calc(0.15s + var(--k) * 0.55s) infinite both; }
@keyframes oc-row { 0% { opacity: 0; translate: -8px 0; } 4% { opacity: 1; translate: 0 0; } 84% { opacity: 1; } 94%, 100% { opacity: 0; } }

/* ---- MPay 06 · identity: landmarks on both faces, matched, scored; liveness and tamper checks pass ---- */
.fc__frame { fill: color-mix(in oklab, var(--white) 5%, transparent); stroke: color-mix(in oklab, var(--white) 22%, transparent); }
.fc__face path { fill: color-mix(in oklab, var(--navy-300) 18%, transparent); stroke: color-mix(in oklab, var(--navy-300) 65%, transparent); stroke-width: 1.2; }
.fc__lm { fill: var(--white); }
.fc__link { fill: none; stroke: color-mix(in oklab, var(--navy-300) 55%, transparent); stroke-dasharray: 100; }
.fc__ring-track { fill: none; stroke: color-mix(in oklab, var(--white) 10%, transparent); stroke-width: 4; }
.fc__ring { fill: none; stroke: var(--navy-300); stroke-width: 4; stroke-linecap: round; stroke-dasharray: 100; stroke-dashoffset: 2; rotate: -90deg; }
.fc__score { font-size: 13px; font-weight: 600; fill: var(--white); }
.fc__check circle { fill: var(--navy-300); }
.fc__check path { fill: none; stroke: var(--ink); stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.viz--face.is-active .fc__lm { animation: v-pop 400ms var(--pop) calc(0.2s + var(--k) * 50ms) both; }
.viz--face.is-active .fc__link { animation: v-draw 600ms var(--enter) calc(0.7s + var(--k) * 80ms) both; }
.viz--face.is-active .fc__ring { animation: fc-ring 900ms var(--enter) 1.1s both; }
@keyframes fc-ring { from { stroke-dashoffset: 100; } }
.viz--face.is-active .fc__score { animation: v-fade 400ms var(--enter) 1.5s both; }
.viz--face.is-active .fc__check { animation: v-pop 450ms var(--pop) calc(1.7s + var(--k) * 220ms) both; }

@media (prefers-reduced-motion: reduce) {
  .det__scan, .fr__scan, .ci__token, .el__cursor { display: none; }
}
