/* CloudXOps immersive pages (services, managed cloud): one continuous story per page.
   Scene palette lives in the --ix-* tokens; the site chrome keeps the global tokens.
   Depth layers, lighting and reveals are driven by --p (0..1), written on each .ix-story
   by /js/story.js; everything here is transforms and opacity. */
:root{
  --ix-base:#05070B; --ix-base-2:#080B11; --ix-base-3:#0B1018;
  --ix-cyan:#00C8FF; --ix-blue:#2575FF; --ix-teal:#16E0C0; --ix-violet:#7458FF;
  --ix-amber:#F2A93B; --ix-red:#FF5A5A;
  --ix-ink:#F7F9FC; --ix-mute:#A8AFBC; --ix-dim:#949CAA;
  --ix-k:var(--ix-cyan); --ix-k-rgb:0,200,255;
}
.ix{color:var(--ix-ink);}
.ix *{box-sizing:border-box;}

/* ---------- type ---------- */
.ix-label{display:inline-flex;align-items:center;gap:12px;font-family:var(--font-mono);font-size:12px;font-weight:600;
  letter-spacing:.26em;text-transform:uppercase;color:var(--ix-k);line-height:1;}
.ix-label::before,.ix-label::after{content:"";width:26px;height:1px;flex:none;background:linear-gradient(90deg,transparent,var(--ix-k));}
.ix-label::after{background:linear-gradient(90deg,var(--ix-k),transparent);}
.ix-label.is-left::before{display:none;}
.ix-h1{font-family:var(--font-display);font-weight:800;font-size:clamp(44px,6.2vw,92px);line-height:1.02;letter-spacing:-.025em;
  color:#fff;margin:22px 0 0;text-wrap:balance;}
/* the same ramp the landing page slogan runs (.hm-slogan .hm-l1 in index.astro): white, then
   a very pale blue, then the soft sky blue the brand uses on type. The hard cyan this used to
   end on is the interface accent, not the heading colour, and it read as the wrong blue. */
.ix-h1 .g,.ix-h2 .g{background:linear-gradient(135deg,#ffffff 0%,#e4f1ff 42%,#8fd8ff 100%);-webkit-background-clip:text;background-clip:text;color:transparent;
  /* background-clip:text clips the fill to the glyphs, but the box it clips FROM is this
     span's own background box, which is the font's content area. A display face with tall
     caps and a tight line can put ink outside it, and any ink outside has no fill behind it
     and comes out transparent: letters cut off at the top. The padding is vertical only and
     this span is inline, so it enlarges the box the gradient is drawn into and changes no
     layout at all. */
  padding:.2em 0 .08em;}
.ix-h2{font-family:var(--font-display);font-weight:800;font-size:clamp(34px,4.4vw,66px);line-height:1.04;letter-spacing:-.022em;color:#fff;margin:18px 0 0;text-wrap:balance;}
.ix-h3{font-family:var(--font-display);font-weight:800;font-size:clamp(24px,2.2vw,34px);line-height:1.12;letter-spacing:-.015em;color:#fff;margin:0;}
.ix-state{font-family:var(--font-display);font-weight:800;font-size:clamp(28px,3.6vw,56px);line-height:1.05;letter-spacing:-.02em;color:#fff;margin:0;text-wrap:balance;}
.ix-sub{font-family:var(--font-light);font-size:clamp(17px,1.3vw,20px);line-height:1.68;color:#d3d9e3;margin:20px 0 0;max-width:62ch;text-wrap:pretty;}
.ix-p{font-family:var(--font-body);font-size:clamp(16px,1.15vw,18px);line-height:1.72;color:var(--ix-mute);margin:14px 0 0;max-width:60ch;}
.ix-meta{display:flex;gap:18px;flex-wrap:wrap;margin-top:26px;font-family:var(--font-mono);font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--ix-dim);}
.ix-meta b{color:#fff;font-family:var(--font-display);font-weight:800;font-size:18px;letter-spacing:0;margin-right:6px;text-transform:none;}
.ix-meta i{width:1px;height:14px;background:rgba(160,230,255,.35);align-self:center;}

/* ---------- buttons (the site's own CTA language) ---------- */
.ix-cta{display:flex;gap:14px;flex-wrap:wrap;margin-top:32px;}
.ix-btn{display:inline-flex;align-items:center;gap:10px;padding:15px 24px;border-radius:14px;font-family:var(--font-display);font-weight:700;font-size:15.5px;
  color:#fff;text-decoration:none;position:relative;isolation:isolate;transition:transform .35s cubic-bezier(.2,.7,.2,1),box-shadow .35s ease;}
.ix-btn.is-primary{background:linear-gradient(135deg,rgba(0,212,170,.28),rgba(34,211,238,.24));border:1px solid rgba(92,225,230,.5);
  box-shadow:0 0 0 1px rgba(0,0,0,.2) inset,0 10px 30px -14px rgba(34,211,238,.6);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);}
.ix-btn.is-primary:hover{transform:translateY(-2px);box-shadow:0 0 0 1px rgba(0,0,0,.2) inset,0 16px 36px -14px rgba(34,211,238,.8);}
.ix-btn.is-ghost{border:1px solid rgba(180,200,255,.22);background:rgba(10,12,20,.35);}
.ix-btn.is-ghost:hover{border-color:rgba(180,220,255,.5);transform:translateY(-2px);}
.ix-btn svg{width:16px;height:16px;transition:transform .35s cubic-bezier(.2,.7,.2,1);}
.ix-btn:hover svg{transform:translateX(4px);}
.ix-btn:focus-visible{outline:2px solid var(--ix-cyan);outline-offset:3px;}

/* ---------- the story: tall wrapper, sticky stage ---------- */
.ix-story{position:relative;height:calc(var(--len,240) * 1vh);--p:0;}
.ix-stage{position:sticky;top:0;height:100vh;height:100svh;overflow:hidden;background:var(--ix-base);}
.ix-stage::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(5,7,11,.55) 0%,rgba(5,7,11,0) 22%,rgba(5,7,11,0) 70%,rgba(5,7,11,.7) 100%);}
/* the stage begins and ends on the page colour so one story flows into the next */
.ix-story::before,.ix-story::after{content:"";position:absolute;left:0;right:0;height:18vh;pointer-events:none;z-index:3;}
.ix-story::before{top:0;background:linear-gradient(180deg,var(--bg),transparent);}
.ix-story::after{bottom:0;background:linear-gradient(0deg,var(--bg),transparent);}
.ix-story.is-hero::before{display:none;}

/* depth layers: a plate that moves and scales a little with --p; --rate is its parallax in
   viewport heights, --zoom its push, --blend how it sits on the plate beneath it */
.ix-layer{position:absolute;top:calc(-7% + var(--dy,0%));left:calc(-7% + var(--dx,0%));width:114%;height:114%;pointer-events:none;
  transform:translate3d(0,calc(var(--p) * var(--rate,0) * -1vh),0) scale(calc(1 + var(--p) * var(--zoom,0)));
  transform-origin:50% 50%;will-change:transform;}
.ix-layer img,.ix-layer picture,.ix-layer canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:var(--pos,50% 50%);display:block;}
.ix-layer.is-screen{mix-blend-mode:screen;}
.ix-layer.is-lighten{mix-blend-mode:lighten;}
/* a wide plate that pans across the stage with the story */
.ix-layer.is-pan img{object-position:calc(var(--p) * 100%) 50%;}
.ix-layer.is-lit{opacity:calc(var(--lit-0,0) + var(--p) * var(--lit-1,1));}
.ix-layer.is-dim{opacity:calc(1 - var(--p) * var(--dim,0.5));}
.ix-glow{position:absolute;inset:0;pointer-events:none;mix-blend-mode:screen;
  background:radial-gradient(50% 42% at var(--gx,52%) var(--gy,48%),rgba(var(--ix-k-rgb),calc(.10 + var(--p) * .16)),transparent 70%);}
.ix-atmos{position:absolute;inset:-10%;pointer-events:none;opacity:.55;
  background:radial-gradient(60% 55% at 30% 70%,rgba(37,117,255,.12),transparent 60%),radial-gradient(45% 40% at 78% 24%,rgba(0,200,255,.10),transparent 60%);
  transform:translate3d(calc(var(--p) * 3%),calc(var(--p) * -4%),0);}
.ix-vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(120% 90% at 50% 50%,transparent 45%,rgba(5,7,11,.72) 100%);}

/* ---------- the copy column over the stage ---------- */
.ix-col{position:absolute;z-index:4;top:0;bottom:0;left:0;width:min(46%,640px);padding:0 0 0 clamp(24px,6vw,96px);display:flex;align-items:center;}
.ix-col.is-right{left:auto;right:0;padding:0 clamp(24px,6vw,96px) 0 0;}
.ix-col-in{position:relative;width:100%;}
.ix-block{position:absolute;left:0;right:0;top:50%;transform:translate3d(0,calc(-50% + 26px),0);opacity:0;pointer-events:none;
  transition:opacity .55s ease,transform .7s cubic-bezier(.2,.7,.2,1);}
.ix-block.is-on{opacity:1;transform:translate3d(0,-50%,0);pointer-events:auto;}
.ix-block.is-static{position:relative;top:auto;transform:none;opacity:1;pointer-events:auto;}
.ix-hero-copy{position:absolute;z-index:4;left:clamp(24px,6vw,96px);right:clamp(24px,6vw,96px);top:50%;transform:translate3d(0,-50%,0);max-width:820px;}
.ix-hero-copy.is-late{opacity:calc(1 - var(--p) * 1.6);transform:translate3d(0,calc(-50% - var(--p) * 60px),0);}
.ix-cue{position:absolute;z-index:4;left:50%;bottom:26px;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;gap:8px;
  font-family:var(--font-mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;color:var(--ix-dim);opacity:calc(1 - var(--p) * 4);}
.ix-cue i{width:1px;height:38px;background:linear-gradient(180deg,var(--ix-cyan),transparent);}

/* ---------- stage labels: filament labels that switch on at a progress ---------- */
.ix-marks{position:absolute;inset:0;z-index:3;pointer-events:none;}
.ix-mark{position:absolute;left:var(--x);top:var(--y);transform:translate3d(-50%,-50%,0);display:flex;align-items:center;gap:10px;
  font-family:var(--font-mono);font-size:12px;letter-spacing:.24em;text-transform:uppercase;color:rgba(247,249,252,.35);white-space:nowrap;
  transition:color .5s ease,opacity .5s ease,transform .6s cubic-bezier(.2,.7,.2,1);opacity:.45;}
.ix-mark::before{content:"";width:8px;height:8px;border:1px solid currentColor;transform:rotate(45deg);flex:none;transition:background .4s ease,box-shadow .4s ease;}
.ix-mark.is-on{color:var(--ix-k);opacity:1;}
.ix-mark.is-on::before{background:var(--ix-k);box-shadow:0 0 14px rgba(var(--ix-k-rgb),.9);}
.ix-mark.is-done{color:var(--ix-teal);}
/* drawn strokes: --d is the local progress written by the engine */
.ix-draw{stroke-dasharray:var(--len);stroke-dashoffset:calc(var(--len) * (1 - var(--d,0)));}

/* ---------- alert plates (the existing popup lines, in depth) ---------- */
.ix-plates{display:grid;gap:12px;perspective:1200px;}
.ix-plate{position:relative;padding:14px 16px 14px;border-radius:8px;
  background:linear-gradient(160deg,rgba(40,140,210,.16) 0%,rgba(12,60,110,.12) 50%,rgba(8,30,70,.18) 100%);
  border:1px solid rgba(150,225,255,.22);-webkit-backdrop-filter:blur(10px) saturate(130%);backdrop-filter:blur(10px) saturate(130%);
  box-shadow:inset 0 1px 0 rgba(200,240,255,.14),0 30px 50px -34px rgba(0,0,0,.9);}
.ix-plate::before,.ix-plate::after{content:"";position:absolute;width:12px;height:12px;border:1px solid rgba(190,240,255,.75);pointer-events:none;}
.ix-plate::before{left:-4px;top:-4px;border-right:0;border-bottom:0;}
.ix-plate::after{right:-4px;bottom:-4px;border-left:0;border-top:0;}
.ix-plate-h{display:flex;justify-content:space-between;gap:12px;font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--ix-k);margin-bottom:8px;}
.ix-plate-h span:last-child{color:var(--ix-dim);letter-spacing:.08em;text-transform:none;}
.ix-plate p{margin:0 0 4px;font-family:var(--font-mono);font-size:13px;line-height:1.55;color:#d7e8f2;}
.ix-plate p.is-mod{color:#f5d76e;}
.ix-plate .ix-res{margin-top:10px;padding-top:10px;border-top:1px solid rgba(22,224,192,.25);font-family:var(--font-body);font-size:14px;color:#eafff9;
  opacity:0;transform:translate3d(0,6px,0);transition:opacity .5s ease .1s,transform .5s ease .1s;}
.ix-plate .ix-res::before{content:"OK";display:inline-block;margin-right:10px;font-family:var(--font-mono);font-size:11px;letter-spacing:.2em;color:var(--ix-teal);}
.ix-story.is-resolved .ix-plate .ix-res,.ix-block.is-resolved .ix-plate .ix-res{opacity:1;transform:none;}
.ix-story.is-resolved .ix-plate p:not(.ix-res),.ix-block.is-resolved .ix-plate p:not(.ix-res){opacity:.45;}

/* ---------- deliverables and stations ---------- */
.ix-list{list-style:none;margin:16px 0 0;padding:0;display:grid;grid-template-columns:1fr 1fr;gap:8px 22px;}
.ix-list li{position:relative;padding-left:18px;font-family:var(--font-body);font-size:15px;line-height:1.5;color:#d5e6f0;}
.ix-list li::before{content:"";position:absolute;left:0;top:.62em;width:8px;height:1px;background:var(--ix-k);box-shadow:0 0 8px rgba(var(--ix-k-rgb),.8);}
.ix-stations{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:18px;}
.ix-station{padding:12px 14px;border-radius:10px;border:1px solid rgba(150,225,255,.14);background:rgba(8,14,26,.45);}
.ix-station h3,.ix-station h4{margin:0;font-family:var(--font-display);font-weight:700;font-size:15px;color:#fff;}
.ix-station p{margin:6px 0 0;font-family:var(--font-body);font-size:13.5px;line-height:1.5;color:var(--ix-mute);}

/* ---------- proof rows ---------- */
.ix-proof{position:relative;padding:clamp(80px,10vw,140px) clamp(24px,6vw,96px);overflow:hidden;}
.ix-proof-bg{position:absolute;inset:0;z-index:0;opacity:.55;}
.ix-proof-bg img{width:100%;height:100%;object-fit:cover;display:block;}
.ix-proof-bg::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,var(--bg),rgba(8,8,16,.35) 30%,rgba(8,8,16,.35) 70%,var(--bg));}
.ix-proof-in{position:relative;z-index:1;max-width:1180px;margin:0 auto;}
.ix-proof-row{display:grid;grid-template-columns:minmax(220px,.9fr) 1.6fr auto;gap:28px;align-items:center;padding:34px 0;border-top:1px solid rgba(180,200,255,.12);}
.ix-proof-row:last-child{border-bottom:1px solid rgba(180,200,255,.12);}
.ix-proof-metric{font-family:var(--font-display);font-weight:800;font-size:clamp(36px,4.6vw,68px);line-height:1;letter-spacing:-.03em;
  background:linear-gradient(100deg,#fff,var(--ix-cyan));-webkit-background-clip:text;background-clip:text;color:transparent;}
.ix-proof-client{font-family:var(--font-mono);font-size:12px;letter-spacing:.22em;text-transform:uppercase;color:var(--ix-dim);margin-bottom:8px;}
.ix-proof-text{font-family:var(--font-body);font-size:17px;line-height:1.6;color:#d3d9e3;margin:0;}
.ix-proof-link{font-family:var(--font-display);font-weight:700;font-size:14.5px;color:#fff;text-decoration:none;display:inline-flex;align-items:center;gap:8px;white-space:nowrap;}
.ix-proof-link svg{width:14px;height:14px;transition:transform .3s ease;}
.ix-proof-link:hover svg{transform:translateX(4px);}

/* ---------- technology ecosystem ---------- */
.ix-eco{position:relative;padding:clamp(70px,9vw,120px) 0;overflow:hidden;}
.ix-eco-bg{position:absolute;inset:0;opacity:.6;}
.ix-eco-bg img{width:100%;height:100%;object-fit:cover;display:block;}
.ix-eco-bg::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,var(--bg),rgba(8,8,16,.2) 30%,rgba(8,8,16,.2) 70%,var(--bg));}
.ix-eco-head{position:relative;z-index:1;max-width:1180px;margin:0 auto 34px;padding:0 24px;}
.ix-eco-band{position:relative;z-index:1;display:grid;grid-template-columns:repeat(8,1fr);gap:14px;max-width:1180px;margin:0 auto;padding:0 24px;}
.ix-eco-mark{position:relative;display:flex;flex-direction:column;align-items:center;gap:12px;padding:22px 10px 16px;border-radius:14px;
  border:1px solid rgba(160,200,255,.12);background:rgba(8,12,22,.5);text-decoration:none;color:var(--ix-dim);
  font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;transition:border-color .35s ease,transform .35s cubic-bezier(.2,.7,.2,1),box-shadow .35s ease;}
.ix-eco-mark img{width:44px;height:44px;object-fit:contain;display:block;}
.ix-eco-mark::after{content:"";position:absolute;left:50%;bottom:-1px;width:0;height:1px;background:linear-gradient(90deg,transparent,var(--ix-cyan),transparent);transform:translateX(-50%);transition:width .4s ease;}
.ix-eco-mark:hover,.ix-eco-mark:focus-visible{border-color:rgba(160,230,255,.45);transform:translateY(-4px);box-shadow:0 0 30px -12px rgba(0,200,255,.6);outline:none;color:#fff;}
.ix-eco-mark:hover::after,.ix-eco-mark:focus-visible::after{width:70%;}

/* ---------- terminal CTA projected into a console ---------- */
.ix-console{position:relative;padding:clamp(70px,9vw,130px) 24px;overflow:hidden;}
.ix-console-bg{position:absolute;inset:0;}
.ix-console-bg img{width:100%;height:100%;object-fit:cover;display:block;}
.ix-console-bg::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,var(--bg),rgba(8,8,16,.15) 25%,rgba(8,8,16,.25) 75%,var(--bg));}
.ix-console .cxo-contact-cta{position:relative;z-index:1;}

/* ---------- reveals ---------- */
.ix-reveal{opacity:0;transform:translate3d(0,22px,0);transition:opacity .8s ease,transform .9s cubic-bezier(.2,.7,.2,1);}
.ix-reveal.is-in{opacity:1;transform:none;}
.ix-reveal[data-delay="1"].is-in{transition-delay:.1s}.ix-reveal[data-delay="2"].is-in{transition-delay:.2s}.ix-reveal[data-delay="3"].is-in{transition-delay:.3s}

/* ---------- code overlay (real DOM) ---------- */
.ix-code{font-family:var(--font-mono);font-size:12.5px;line-height:1.6;color:#cfe6f5;margin:0;white-space:pre;
  padding:16px 18px;border-radius:10px;background:rgba(6,10,18,.6);border:1px solid rgba(150,225,255,.16);overflow:hidden;}
.ix-code .k{color:var(--ix-violet);} .ix-code .s{color:var(--ix-teal);} .ix-code .n{color:var(--ix-amber);} .ix-code .c{color:var(--ix-dim);}
.ix-code span[data-at]{opacity:.18;transition:opacity .3s ease;}
.ix-code span[data-at].is-on{opacity:1;}

/* ---------- charts (SVG, fixed data) ---------- */
.ix-chart{width:100%;height:auto;display:block;}
.ix-chart .grid{stroke:rgba(160,200,255,.12);stroke-width:1;}
.ix-chart .line{fill:none;stroke:var(--ix-k);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.ix-chart .area{fill:url(#ixArea);}

/* ---------- mobile composition ---------- */
@media (max-width:860px){
  .ix-story{height:calc(var(--len-m,var(--len,240)) * 1svh);}
  .ix-layer{transform:translate3d(0,calc(var(--p) * var(--rate,0) * -0.5vh),0) scale(calc(1 + var(--p) * var(--zoom,0) * .5));}
  .ix-col,.ix-col.is-right{left:0;right:0;width:auto;padding:0 18px;top:auto;bottom:0;height:62svh;align-items:flex-end;}
  .ix-col-in{padding-bottom:26px;}
  .ix-block{top:auto;bottom:0;transform:translate3d(0,18px,0);}
  .ix-block.is-on{transform:none;}
  .ix-stage::after{background:linear-gradient(180deg,rgba(5,7,11,.5) 0%,rgba(5,7,11,0) 18%,rgba(5,7,11,.25) 45%,rgba(5,7,11,.88) 100%);}
  .ix-hero-copy{left:18px;right:18px;top:auto;bottom:16svh;transform:none;}
  .ix-hero-copy.is-late{opacity:calc(1 - var(--p) * 1.6);transform:translate3d(0,calc(var(--p) * -40px),0);}
  .ix-h1{font-size:clamp(34px,10vw,48px);}
  .ix-h2{font-size:clamp(28px,7.6vw,38px);}
  .ix-state{font-size:clamp(24px,6.6vw,34px);}
  .ix-list{grid-template-columns:1fr;gap:6px;}
  .ix-stations{grid-template-columns:1fr 1fr;gap:8px;}
  .ix-station{padding:10px 12px;} .ix-station h3,.ix-station h4{font-size:14px;} .ix-station p{display:none;}
  .ix-plates{gap:8px;} .ix-plate p{font-size:12px;} .ix-plate{transform:none;}
  .ix-mark{font-size:10px;letter-spacing:.18em;}
  .ix-proof-row{grid-template-columns:1fr;gap:10px;padding:26px 0;}
  .ix-eco-band{grid-template-columns:repeat(4,1fr);gap:10px;}
  .ix-eco-mark{padding:16px 6px 12px;font-size:10px;} .ix-eco-mark img{width:34px;height:34px;}
  .ix-cue{display:none;}
}
@media (prefers-reduced-motion:reduce){
  .ix-layer{transform:none!important;}
  .ix-block,.ix-mark,.ix-reveal,.ix-plate .ix-res{transition:none!important;}
  .ix-block.is-static,.ix-block.is-on{opacity:1;}
}

/* ---------- page-specific: the services hero transition ---------- */
.ix-statement{position:absolute;z-index:4;left:clamp(24px,6vw,96px);right:clamp(24px,6vw,96px);bottom:18vh;max-width:820px;opacity:0;transform:translate3d(0,24px,0);
  transition:opacity .7s ease,transform .9s cubic-bezier(.2,.7,.2,1);pointer-events:none;}
.ix-statement.is-on{opacity:1;transform:none;}
.ix-paths{position:absolute;inset:0;width:100%;height:100%;z-index:2;pointer-events:none;}
.ix-paths path{fill:none;stroke:var(--ix-cyan);stroke-width:1.2;stroke-linecap:round;opacity:.85;filter:drop-shadow(0 0 6px rgba(0,200,255,.55));}
.ix-paths circle{fill:var(--ix-cyan);opacity:calc(var(--d,0));}
@media (max-width:860px){ .ix-statement{bottom:auto;top:12svh;} .ix-paths{display:none;} }

/* ---------- the practice displays: real diagrams over the scene ---------- */
.ix-holo{position:absolute;z-index:3;right:5%;top:50%;transform:translate3d(0,-50%,0);width:min(46%,720px);display:grid;gap:14px;pointer-events:none;}
.ix-holo.is-left{right:auto;left:5%;}
.ix-holo-panel{position:relative;padding:18px 18px 14px;border-radius:10px;
  background:linear-gradient(160deg,rgba(40,140,210,.14) 0%,rgba(12,60,110,.10) 50%,rgba(8,30,70,.16) 100%);
  border:1px solid rgba(150,225,255,.2);-webkit-backdrop-filter:blur(8px) saturate(130%);backdrop-filter:blur(8px) saturate(130%);
  box-shadow:inset 0 1px 0 rgba(200,240,255,.12),0 30px 60px -40px rgba(0,0,0,.9);}
.ix-holo-panel::before,.ix-holo-panel::after{content:"";position:absolute;width:12px;height:12px;border:1px solid rgba(190,240,255,.75);pointer-events:none;}
.ix-holo-panel::before{left:-4px;top:-4px;border-right:0;border-bottom:0;}
.ix-holo-panel::after{right:-4px;bottom:-4px;border-left:0;border-top:0;}
.ix-holo-t{display:flex;justify-content:space-between;font-family:var(--font-mono);font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--ix-k);margin-bottom:10px;}
.ix-holo-t span:last-child{color:var(--ix-dim);letter-spacing:.06em;text-transform:none;}
.ix-holo svg{width:100%;height:auto;display:block;overflow:visible;font-family:var(--font-mono);}
.ix-holo text{fill:#d7e8f2;font-size:20px;letter-spacing:.02em;}
.ix-holo .name{fill:var(--ix-dim);font-size:20px;letter-spacing:.1em;text-transform:uppercase;}
/* pipeline */
.ix-pipe .rail{stroke:rgba(160,215,255,.18);stroke-width:1.5;}
.ix-pipe .rail.is-lit{stroke:var(--ix-k);stroke-width:1.5;stroke-dasharray:var(--len);stroke-dashoffset:calc(var(--len) * (1 - var(--d,0)));filter:drop-shadow(0 0 6px rgba(var(--ix-k-rgb),.8));}
.ix-pipe .box{fill:rgba(6,11,20,.96);stroke:rgba(160,215,255,.28);stroke-width:1;transition:stroke .4s ease,fill .4s ease;}
.ix-pipe .tick{fill:none;stroke:var(--ix-teal);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset .5s ease .2s;}
.ix-pipe .bar{fill:rgba(160,215,255,.18);transform-origin:left center;transform:scaleX(0);transition:transform .6s ease;}
.ix-pipe .stage.is-on .box{stroke:var(--ix-k);fill:rgba(6,11,20,.96);}
.ix-pipe .stage.is-on .bar{fill:var(--ix-k);transform:scaleX(1);}
.ix-pipe .stage.is-on:has(+ .stage.is-on) .box,.ix-pipe .stage.is-last.is-on .box{stroke:var(--ix-teal);fill:rgba(6,11,20,.96);}
.ix-pipe .stage.is-on:has(+ .stage.is-on) .tick{stroke-dashoffset:0;}
.ix-pipe .stage.is-on:has(+ .stage.is-on) .bar{fill:var(--ix-teal);}
.ix-pipe .stage.is-last.is-on .tick{stroke-dashoffset:0;}
.ix-pipe .stage.is-on:has(+ .stage.is-on) .bar,.ix-pipe .stage.is-last.is-on .bar{fill:var(--ix-teal);}
.ix-pipe .packet{transform:translateX(calc(var(--d,0) * var(--travel)));}
.ix-pipe .packet path{fill:#fff;filter:drop-shadow(0 0 6px rgba(var(--ix-k-rgb),1));opacity:calc(var(--d,0) * 1.5);}
/* cluster */
.ix-cluster .box{fill:rgba(8,14,26,.7);stroke:rgba(160,215,255,.22);stroke-width:1;}
.ix-cluster .link{stroke:rgba(160,215,255,.22);stroke-width:1;}
.ix-cluster .pod rect{fill:rgba(var(--ix-k-rgb),.08);stroke:rgba(160,215,255,.25);stroke-width:1;opacity:0;transform:scale(.6);transform-box:fill-box;transform-origin:center;transition:opacity .4s ease,transform .5s cubic-bezier(.2,.7,.2,1),fill .4s ease,stroke .4s ease;}
.ix-cluster .pod.is-on rect{opacity:1;transform:none;fill:rgba(var(--ix-k-rgb),.28);stroke:var(--ix-k);filter:drop-shadow(0 0 8px rgba(var(--ix-k-rgb),.6));}
.ix-cluster .pod .fail{opacity:0;transition:opacity .4s ease;}
.ix-cluster .pod.is-on .fail,.ix-cluster .pod .fail{fill:rgba(255,90,90,.42);stroke:var(--ix-red);}
.ix-cluster .pod.is-failing .fail.is-on ~ *,.ix-cluster .pod.is-failing:has(.fail.is-on) rect:first-child{stroke:rgba(255,90,90,.5);}
.ix-cluster .pod .fail.is-on{opacity:1;}
.ix-cluster .pod.is-failing:has(.fail.is-on) rect:first-child{opacity:.35;}
.ix-cluster .pod.is-heal.is-on rect{fill:rgba(22,224,192,.28);stroke:var(--ix-teal);}
.ix-cluster .hud .val{fill:#fff;font-size:22px;font-family:var(--font-display);font-weight:800;opacity:0;transition:opacity .3s ease;}
.ix-cluster .hud .val.small{font-size:19px;font-family:var(--font-mono);font-weight:400;fill:var(--ix-k);}
.ix-cluster .hud .val.is-warn{fill:var(--ix-amber);}
.ix-cluster .hud .val.is-on{opacity:1;}
/* dependency graph */
.ix-graph .edge{stroke:rgba(160,215,255,.2);stroke-width:1;}
.ix-graph .gnode rect{fill:rgba(8,14,26,.7);stroke:rgba(160,215,255,.28);stroke-width:1;transition:stroke .5s ease,fill .5s ease;}
.ix-graph .gnode text{font-size:20px;fill:#d7e8f2;}
.ix-graph .gnode.is-warn rect{stroke:rgba(242,169,59,.7);fill:rgba(242,169,59,.10);}
.ix-graph .gnode.is-on rect{stroke:var(--ix-k);fill:rgba(var(--ix-k-rgb),.18);filter:drop-shadow(0 0 8px rgba(var(--ix-k-rgb),.5));}
.ix-graph .gnode.is-warn.is-on rect{stroke:var(--ix-teal);fill:rgba(22,224,192,.16);}
/* migration board */
.ix-board .panel{fill:rgba(8,14,26,.6);stroke:rgba(160,215,255,.22);stroke-width:1;}
.ix-board .is-target .panel{stroke:rgba(var(--ix-k-rgb),.45);}
.ix-board .rail{stroke:rgba(160,215,255,.18);stroke-width:1.5;}
.ix-board .rail.is-lit{stroke:var(--ix-k);stroke-dasharray:var(--len);stroke-dashoffset:calc(var(--len) * (1 - var(--d,0)));filter:drop-shadow(0 0 6px rgba(var(--ix-k-rgb),.8));}
.ix-board .load rect{fill:rgba(242,169,59,.12);stroke:rgba(242,169,59,.6);stroke-width:1;transition:opacity .5s ease,transform .6s cubic-bezier(.2,.7,.2,1);}
.ix-board .load text{font-size:19px;fill:#d7e8f2;transition:opacity .5s ease;}
.ix-board .load.is-src.is-on rect,.ix-board .load.is-src.is-on text{opacity:.18;}
.ix-board .load.is-dst rect,.ix-board .load.is-dst text{opacity:0;}
.ix-board .load.is-dst.is-on rect{opacity:1;fill:rgba(var(--ix-k-rgb),.2);stroke:var(--ix-k);filter:drop-shadow(0 0 8px rgba(var(--ix-k-rgb),.5));}
.ix-board .load.is-dst.is-on text{opacity:1;}
/* terminal */
.ix-log{border-radius:10px;overflow:hidden;background:rgba(5,9,16,.78);border:1px solid rgba(150,225,255,.18);box-shadow:0 30px 60px -40px rgba(0,0,0,.9);}
.ix-log-h{display:flex;justify-content:space-between;align-items:center;padding:8px 14px;font-family:var(--font-mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--ix-dim);border-bottom:1px solid rgba(150,225,255,.12);}
.ix-log-h i{width:26px;height:1px;background:linear-gradient(90deg,var(--ix-k),transparent);}
.ix-log-b{padding:10px 14px 12px;font-family:var(--font-mono);font-size:12.5px;line-height:1.7;}
.ix-log-l{opacity:0;transform:translate3d(0,6px,0);transition:opacity .35s ease,transform .35s ease;white-space:pre-wrap;color:#b9c8d6;}
.ix-log-l.is-on{opacity:1;transform:none;}
.ix-log-l.is-cmd{color:#fff;} .ix-log-l.is-cmd::before{content:"$ ";color:var(--ix-k);}
.ix-log-l.is-ok{color:var(--ix-teal);} .ix-log-l.is-warn{color:var(--ix-amber);} .ix-log-l.is-info{color:#dbe9f5;}
@media (max-width:860px){
  .ix-holo,.ix-holo.is-left{left:14px;right:14px;width:auto;top:9svh;transform:none;gap:8px;}
  .ix-holo-panel{padding:12px 12px 10px;} .ix-holo text{font-size:13px;} .ix-log{display:none;}
}

/* ---------- sub-scenes that crossfade with the story (capabilities) ---------- */
.ix-sub-wrap{position:absolute;inset:0;opacity:0;transition:opacity .9s ease;pointer-events:none;}
.ix-sub-wrap.is-on{opacity:1;}
.ix-layer.is-poster{z-index:0;}
.ix-layer.is-frames{z-index:1;}
.ix-dimmer{position:absolute;inset:0;pointer-events:none;background:rgba(5,7,11,var(--dim-a,.45));}
/* a figure that stands in the scene (a kept statistic) */
.ix-figure{position:absolute;z-index:3;right:8%;bottom:16%;text-align:right;pointer-events:none;opacity:0;transform:translate3d(0,14px,0);transition:opacity .6s ease,transform .7s cubic-bezier(.2,.7,.2,1);}
.ix-figure.is-on{opacity:1;transform:none;}
.ix-figure b{display:block;font-family:var(--font-display);font-weight:800;font-size:clamp(44px,6vw,96px);line-height:1;letter-spacing:-.03em;
  background:linear-gradient(100deg,#fff,var(--ix-k));-webkit-background-clip:text;background-clip:text;color:transparent;}
.ix-figure span{display:block;margin-top:8px;font-family:var(--font-mono);font-size:12px;letter-spacing:.22em;text-transform:uppercase;color:var(--ix-dim);}
.ix-logo{height:34px;width:auto;display:block;margin-bottom:18px;}
.ix-logo.is-wide{height:40px;}
/* chart in the practice panel */
.ix-chart .grid{stroke:rgba(160,200,255,.12);stroke-width:1;}
.ix-chart .line{fill:none;stroke:var(--ix-k);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:var(--len);stroke-dashoffset:calc(var(--len) * (1 - var(--d,0)));}
.ix-chart .line.is-warn{stroke:var(--ix-amber);}
.ix-chart .band{fill:rgba(var(--ix-k-rgb),.08);}
@media (max-width:860px){ .ix-figure{right:18px;bottom:auto;top:10svh;} .ix-figure b{font-size:40px;} }
/* practice panels that sit low in the scene, and stacked panels that swap with the story */
.ix-holo.is-low{top:auto;bottom:8%;transform:none;width:min(38%,560px);}
.ix-holo.is-stack>.ix-block{grid-area:1/1;align-self:end;}
.ix-block.is-flow{position:relative;top:auto;left:auto;right:auto;transform:translate3d(0,18px,0);}
.ix-block.is-flow.is-on{transform:none;}
@media (max-width:860px){ .ix-holo.is-low{top:9svh;bottom:auto;width:auto;} }
/* phones: two plates per block, tighter plates, taller copy columns where the copy is long */
@media (max-width:860px){
  .ix-plates .ix-plate:nth-child(n+3){display:none;}
  .ix-plate{padding:10px 12px 10px;} .ix-plate p{font-size:12px;line-height:1.45;} .ix-plate .ix-res{font-size:13px;margin-top:8px;padding-top:8px;}
  .ix-chapter .ix-col{height:68svh;}
  .ix-models .ix-col{height:74svh;} .ix-models .ix-p{font-size:14px;line-height:1.5;}
}
@media (max-width:860px){ .ix-models .ix-dimmer{--dim-a:.66;} }
/* phones and tablets: flat glass without backdrop blur (blur over a canvas that repaints on
   every scroll frame is the most expensive thing on a phone GPU), and a darker fill so
   mono text reads over the bright parts of a scene */
@media (max-width:860px){
  .ix-plate,.ix-holo-panel{-webkit-backdrop-filter:none;backdrop-filter:none;background:rgba(9,16,28,.84);}
  .ix-log{-webkit-backdrop-filter:none;backdrop-filter:none;}
}
/* phones: the scene marks have no clear space between the panel and the copy, so they stay off */
@media (max-width:860px){ .ix-chapter .ix-marks,.ix-hero .ix-marks,.ix-caps .ix-marks,.ix-models .ix-marks,.ix-providers .ix-marks{display:none;} }

/* ---------- second pass after the phone audit (2026-09-20) ---------- */
/* the frame canvas carries z-index 1, so every overlay that must darken it needs a higher one */
.ix-dimmer,.ix-vignette,.ix-atmos,.ix-glow{z-index:2;}
.ix-stage::after{z-index:2;}
/* On a film stage the clip is the point, so nothing is allowed to sit over it except where
   the copy needs a ground to be read against. The full width top and bottom scrim is replaced
   by one soft pool in the bottom left corner, under the text and nowhere near his face, which
   is centre right. Everything past about two thirds across is left completely clear. */
/* Nothing is laid over the film at all now. The copy carries its own glass instead. */
.ix-story.has-reel .ix-stage::after{background:none;}
/* The text container IS the blur zone: a pane of glass exactly the size of the copy and not one
   pixel wider, so the words have something to sit on and the rest of the shot, his face above
   all, is untouched. The blur has to be on this element and not on a child: the copy fades out
   as the page scrolls, and an ancestor carrying an opacity gives a backdrop filter nothing to
   sample, which is why a pseudo-element version of this showed no blur at all. */
.ix-story.has-reel .ix-hero-copy,
.ix-story.has-reel .ix-statement{
  padding:26px 34px 30px;border-radius:26px;
  -webkit-backdrop-filter:blur(22px) saturate(112%) brightness(.54);
  backdrop-filter:blur(22px) saturate(112%) brightness(.54);
  background:linear-gradient(170deg,rgba(6,12,22,.20) 0%,rgba(6,12,22,.36) 100%);
  border:1px solid rgba(175,220,255,.14);
  box-shadow:0 26px 60px -34px rgba(0,0,0,.8);}
/* the closing statement is one line of display type and only needs the width it uses */
.ix-story.has-reel .ix-statement{width:fit-content;max-width:min(820px,58vw);}
/* While a clip is held the copy's words are hidden but the copy element stays, so the pane has
   to be stripped with them or it sits over the film as an empty grey slab for the whole shot.
   That is exactly the placeholder look the owner has rejected twice, arriving this time from
   the stylesheet rather than from the model. */
.ix-stage.is-held .ix-hero-copy,.ix-stage.is-held .ix-statement,
.ix-story.has-reel .ix-statement:not(.is-on){
  background:none;-webkit-backdrop-filter:none;backdrop-filter:none;
  border-color:transparent;box-shadow:none;}
/* a browser without backdrop-filter still gets a readable ground */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .ix-story.has-reel .ix-hero-copy,.ix-story.has-reel .ix-statement{
    background:linear-gradient(170deg,rgba(6,12,22,.74),rgba(6,12,22,.84));}
}
/* The copy used to carry its own ground: a 100vw wide dark ramp on ::before, anchored to the
   copy box rather than to the screen. The copy is inset 96px, so the ramp started 96px in at
   84 percent black and washed right across the whole shot and over his face, and its left end
   was a hard vertical step. That is the breaking line down the left of every shot. The pool
   above does this job now, in the corner where the text actually is, so the ramp goes. */
.ix-story.has-reel .ix-hero-copy::before,
.ix-story.has-reel .ix-statement::before,
.ix-story.has-reel .ix-col::before{display:none;}
/* The ambient field is the blurred colour spill that stands in for the margin round a clip.
   That only has a job on a phone, where the clip is a band inside a taller stage. On a desktop
   the clip fills the stage, so the field only showed as a lighter blurred rim down the sides
   where the clip's own edge fade let it through, with a seam where the two met. */
.ix-layer.is-reel.is-playing .ix-amb,.ix-layer.is-reel.is-done .ix-amb{opacity:0;}
/* capability marks are range gated: off means invisible, not resting at .45 */
.ix-caps .ix-mark{opacity:0;}
.ix-caps .ix-mark.is-on{opacity:1;}
@media (max-width:860px){
  /* no parallax on phones: the layer is the stage, so the canvas is exactly the screen's pixels */
  .ix-layer{top:0;left:0;width:100%;height:100%;transform:none;}
  .ix-layer.is-pan img{object-position:50% 50%;}
  /* the sticky header is 92 px tall and sits over the stage: nothing pinned to the top may hide under it */
  .ix-holo,.ix-holo.is-left,.ix-holo.is-low{top:calc(var(--header-h,92px) + 10px);}
  .ix-figure{top:calc(var(--header-h,92px) + 8px);}
  .ix-statement{top:calc(var(--header-h,92px) + 16px);}
  /* copy blocks end above the gesture bar, not on the last pixel row */
  .ix-col-in{padding-bottom:0;}
  .ix-block{bottom:calc(26px + env(safe-area-inset-bottom,0px));}
  /* diagram labels at a readable size once the panel is 330 px wide */
  .ix-holo text{font-size:20px;} .ix-holo .name{font-size:16px;}
  /* the capability terminal is that capability's only visual on phones: show its first lines */
  .ix-caps .ix-log{display:block;} .ix-caps .ix-log-l:nth-child(n+5){display:none;}
  /* tools grid: three columns, labels that wrap instead of running past the card */
  .ix-eco-band{grid-template-columns:repeat(3,1fr);gap:8px;}
  .ix-eco-mark{font-size:9px;letter-spacing:.06em;overflow-wrap:anywhere;text-align:center;padding:14px 4px 10px;}
  /* hero meta stacks, no dangling dividers */
  .ix-meta{flex-direction:column;gap:6px;} .ix-meta i{display:none;}
}

/* ---------- third pass after the tablet and Safari audit (2026-09-20) ---------- */
/* the layer needs only a small margin for its parallax (at most 1vh) and push (2.5%): 106%, not 114% */
.ix-layer{top:calc(-3% + var(--dy,0%));left:calc(-3% + var(--dx,0%));width:106%;height:106%;will-change:auto;}
.ix-story.is-live .ix-layer{will-change:transform;}
/* the strip of page background a collapsing phone toolbar reveals under a stage */
.ix-story{background:var(--ix-base);}
/* range-gated marks are invisible when off, on every story */
.ix-mark[data-from]:not(.is-on){opacity:0;}
/* the copy column centres in the space below the sticky header, not behind it (desktop; on phones the column is bottom-anchored) */
@media (min-width:861px){ .ix-col{top:var(--header-h,92px);} }
/* short landscape screens (tablets, 768 px laptops): two plates per block */
@media (max-height:900px) and (min-width:861px){ .ix-plates .ix-plate:nth-child(n+3){display:none;} }
/* the model figures stand on the left, away from the right-hand copy column */
.ix-models .ix-figure{right:auto;left:8%;text-align:left;}
@media (max-width:860px){
  .ix-models .ix-figure{left:auto;right:18px;text-align:right;}
  /* a hero that fits a 660 px phone viewport: smaller title and sub, one compact meta row, side by side buttons */
  .ix-hero-copy{bottom:8svh;}
  .ix-h1{font-size:clamp(30px,8.6vw,40px);}
  .ix-sub{font-size:15px;line-height:1.5;margin-top:14px;}
  .ix-meta{flex-direction:row;flex-wrap:wrap;gap:8px 14px;font-size:10px;margin-top:14px;} .ix-meta b{font-size:14px;}
  .ix-cta{margin-top:18px;gap:10px;} .ix-btn{padding:12px 16px;font-size:14px;}
}
/* the hero is the first screen: it sits under the glass header rather than starting below it,
   so the stage is the whole viewport and nothing anchored to its bottom hides under the fold */
.ix-story.is-hero{margin-top:calc(-1 * var(--header-h,92px));}

/* ---------- fourth pass after the content and motion audits (2026-09-20) ---------- */
/* blocks leave fast and arrive after a beat, so two headings never read at once; an off
   block is also invisible to focus */
.ix-block{transition:opacity .16s ease,transform .16s ease,visibility 0s linear .16s;visibility:hidden;}
.ix-block.is-on{transition:opacity .26s ease .08s,transform .4s cubic-bezier(.2,.7,.2,1) .08s,visibility 0s;visibility:visible;}
.ix-figure{transition:opacity .16s ease,transform .16s ease;}
.ix-figure.is-on{transition:opacity .26s ease .08s,transform .4s cubic-bezier(.2,.7,.2,1) .08s;}
/* arrival reveals quick enough to read on a flick */
.ix-reveal{transition:opacity .35s ease,transform .4s ease;}
.ix-reveal[data-delay="1"].is-in{transition-delay:.05s}.ix-reveal[data-delay="2"].is-in{transition-delay:.1s}.ix-reveal[data-delay="3"].is-in{transition-delay:.15s}
/* Phones and tablets, outside the desktop film frame: the sitewide arrival (site.css
   .cx-reveal), 8px over .3s as the block crosses the bottom edge of the screen (story.js),
   steps of 40ms. A flat offset, so a block waiting below the screen holds no layer. */
@media not all and (min-width:1025px) and (hover:hover) and (pointer:fine){
  .ix-reveal{transform:translateY(8px);transition:opacity .3s cubic-bezier(.2,.7,.2,1),transform .3s cubic-bezier(.2,.7,.2,1);}
  .ix-reveal.is-in{transform:none;}
  .ix-reveal[data-delay="1"].is-in{transition-delay:.04s}.ix-reveal[data-delay="2"].is-in{transition-delay:.08s}.ix-reveal[data-delay="3"].is-in{transition-delay:.12s}
}
/* the hero copy holds for the first fifth of the story, then fades; the statement follows */
.ix-hero-copy.is-late{opacity:calc(1 - max(0, var(--p) - .2) * 1.6);}
/* portrait posters are centred like the portrait frames */
@media (max-width:860px), (orientation:portrait){ .ix-layer.has-m img{object-position:50% 50%;} }
.ix-stage{contain:paint;}
.ix-sub-wrap{will-change:opacity;}
@media (min-width:861px){
  /* a scrim under the copy column so body text reads over bright scene areas */
  .ix-col::before{content:"";position:absolute;top:-10%;bottom:-10%;left:-30%;right:-8%;pointer-events:none;z-index:-1;
    background:linear-gradient(90deg,rgba(5,7,11,.82) 0%,rgba(5,7,11,.55) 70%,rgba(5,7,11,0) 100%);}
  .ix-col.is-right::before{left:-8%;right:-30%;background:linear-gradient(270deg,rgba(5,7,11,.82) 0%,rgba(5,7,11,.55) 70%,rgba(5,7,11,0) 100%);}
  /* diagram labels at a readable size on desktop panels */
  /* short landscape screens keep every plate, compact */
  @media (max-height:900px){ .ix-plates .ix-plate:nth-child(n+3){display:block;} .ix-plate{padding:9px 12px;} .ix-plate p{font-size:12px;line-height:1.4;} }
}
@media (max-width:860px){
  /* every plate and every station text on phones, compact */
  .ix-plates{gap:8px;} .ix-plates .ix-plate:nth-child(n+3){display:block;}
  .ix-plate{padding:8px 10px;} .ix-plate-h{font-size:10px;margin-bottom:5px;} .ix-plate p{font-size:11.5px;line-height:1.4;margin-bottom:2px;}
  .ix-plate .ix-res{font-size:12px;margin-top:6px;padding-top:6px;}
  .ix-station p{display:block;font-size:12px;line-height:1.4;}
}
/* static layout: no JavaScript, or a reader with reduced motion. Every block in flow, the
   stage a normal section over its poster, no frames, no sticky. */
html:not(.ix-js) .ix-story,html.ix-static .ix-story{height:auto!important;margin-top:0;}
html:not(.ix-js) .ix-stage,html.ix-static .ix-stage{position:relative;height:auto;min-height:100svh;overflow:hidden;contain:none;}
html:not(.ix-js) canvas.ix-frames,html.ix-static canvas.ix-frames,html:not(.ix-js) .ix-sub-wrap,html.ix-static .ix-sub-wrap,html:not(.ix-js) .ix-marks,html.ix-static .ix-marks,html:not(.ix-js) .ix-cue,html.ix-static .ix-cue{display:none;}
html:not(.ix-js) .ix-col,html.ix-static .ix-col,html:not(.ix-js) .ix-col.is-right,html.ix-static .ix-col.is-right{position:relative;top:auto;bottom:auto;left:auto;right:auto;width:auto;max-width:860px;height:auto;padding:80px clamp(24px,6vw,96px) 40px;display:block;}
html:not(.ix-js) .ix-col::before,html.ix-static .ix-col::before{display:none;}
html:not(.ix-js) .ix-col-in,html.ix-static .ix-col-in{padding:0;}
html:not(.ix-js) .ix-block,html.ix-static .ix-block{position:relative;top:auto;bottom:auto;left:auto;right:auto;transform:none;opacity:1;visibility:visible;pointer-events:auto;margin:0 0 48px;transition:none;}
html:not(.ix-js) .ix-hero-copy,html.ix-static .ix-hero-copy{position:relative;top:auto;bottom:auto;left:auto;right:auto;transform:none;opacity:1!important;max-width:860px;padding:140px clamp(24px,6vw,96px) 40px;}
html:not(.ix-js) .ix-statement,html.ix-static .ix-statement{position:relative;top:auto;bottom:auto;left:auto;right:auto;opacity:1;transform:none;max-width:860px;padding:0 clamp(24px,6vw,96px) 80px;}
html:not(.ix-js) .ix-holo,html.ix-static .ix-holo,html:not(.ix-js) .ix-holo.is-low,html.ix-static .ix-holo.is-low{position:relative;top:auto;bottom:auto;left:auto;right:auto;width:auto;max-width:720px;margin:0 clamp(24px,6vw,96px) 60px;transform:none;}
html:not(.ix-js) .ix-holo .ix-block,html.ix-static .ix-holo .ix-block{margin:0 0 14px;}
html:not(.ix-js) .ix-figure,html.ix-static .ix-figure{position:relative;top:auto;bottom:auto;left:auto;right:auto;opacity:1;transform:none;text-align:left;margin:0 clamp(24px,6vw,96px) 40px;}
html:not(.ix-js) .ix-reveal,html.ix-static .ix-reveal{opacity:1!important;transform:none!important;}
html:not(.ix-js) .ix-log,html.ix-static .ix-log{display:block;}
html:not(.ix-js) .ix-plate .ix-res,html.ix-static .ix-plate .ix-res{opacity:1;transform:none;}
html:not(.ix-js) .ix-log-l,html.ix-static .ix-log-l,html:not(.ix-js) .ix-mark,html.ix-static .ix-mark{opacity:1;}

/* ---------- fifth pass (2026-09-20): phones stack, panels are never empty ---------- */
/* Panels always show their destination as a ghost, so a container is never an empty box:
   a terminal shows its scrollback dim, a node shows its pod slots, a landing zone shows
   the slots the workloads will land in. */
.ix-log-l{opacity:.17;}
.ix-cluster .pod rect{opacity:.2;transform:none;fill:rgba(160,215,255,.05);}
.ix-board .load.is-dst rect{opacity:.07;}
.ix-board .load.is-dst text{opacity:0;}
/* panels read over a bright scene: solid ink, not a tint */
.ix-holo-panel,.ix-log{background:rgba(6,11,20,.9);}
.ix-plate{background:rgba(6,11,20,.86);}

@media (max-width:860px){
  /* The scene is a block at the top of each section and the copy flows beneath it. Nothing
     is pinned or absolutely positioned, so no two elements can overlap. */
  .ix-story{height:auto!important;margin-top:0;}
  .ix-stage{position:relative;height:auto;min-height:0;overflow:visible;contain:none;background:transparent;}
  .ix-stage::before{content:"";display:block;height:58svh;}
  .ix-story.is-hero .ix-stage::before{height:76svh;}
  .ix-stage::after{display:none;}
  .ix-layer,canvas.ix-frames,.ix-dimmer,.ix-vignette,.ix-atmos,.ix-glow,.ix-sub-wrap{
    position:absolute;top:0;left:0;width:100%;height:58svh;transform:none;}
  .ix-story.is-hero .ix-layer,.ix-story.is-hero canvas.ix-frames,.ix-story.is-hero .ix-dimmer,
  .ix-story.is-hero .ix-vignette,.ix-story.is-hero .ix-atmos,.ix-story.is-hero .ix-glow{height:76svh;}
  .ix-layer img{height:100%;}
  /* a gradient that carries the scene into the copy below it */
  .ix-stage::before{background:linear-gradient(180deg,rgba(5,7,11,0) 55%,rgba(5,7,11,.7) 82%,var(--ix-base) 100%);position:relative;z-index:3;pointer-events:none;}
  /* the copy column and every block in normal flow */
  .ix-col,.ix-col.is-right,.ix-chapter .ix-col,.ix-models .ix-col,.ix-caps .ix-col,.ix-providers .ix-col{
    position:relative;top:auto;bottom:auto;left:auto;right:auto;width:auto;height:auto;min-height:0;
    padding:4px 18px 8px;display:block;align-items:initial;}
  .ix-col::before{display:none;}
  .ix-col-in{padding:0;}
  .ix-block{position:relative;top:auto;bottom:auto;left:auto;right:auto;transform:none;opacity:1;visibility:visible;
    pointer-events:auto;margin:0 0 34px;transition:none;}
  .ix-block:last-child{margin-bottom:8px;}
  .ix-hero-copy{position:relative;top:auto;bottom:auto;left:auto;right:auto;transform:none;padding:18px 18px 8px;max-width:none;}
  .ix-hero-copy.is-late{opacity:1;transform:none;}
  .ix-statement{position:relative;top:auto;bottom:auto;left:auto;right:auto;opacity:1;transform:none;padding:0 18px 18px;}
  .ix-holo,.ix-holo.is-left,.ix-holo.is-low{position:relative;top:auto;bottom:auto;left:auto;right:auto;width:auto;
    transform:none;margin:0 18px 26px;gap:10px;}
  .ix-holo.is-stack>.ix-block{grid-area:auto;}
  .ix-figure{position:relative;top:auto;bottom:auto;left:auto;right:auto;opacity:1;transform:none;text-align:left;
    margin:0 18px 22px;}
  .ix-figure b{font-size:38px;}
  .ix-cue{display:none;}
  /* the scene keeps its own marks off: they belong to a pinned composition */
  .ix-marks{display:none;}
  /* readable sizes in the stacked layout */
  .ix-h1{font-size:clamp(32px,9vw,42px);}
  .ix-h2{font-size:clamp(26px,7.4vw,34px);}
  .ix-sub{font-size:16px;line-height:1.55;}
  .ix-p{font-size:15px;line-height:1.6;}
  .ix-plates{gap:10px;}
  .ix-plate{padding:11px 13px;} .ix-plate p{font-size:12.5px;line-height:1.5;}
  .ix-log{display:block;} .ix-log-l{font-size:12px;}
  .ix-holo text{font-size:22px;} .ix-holo .name{font-size:17px;}
}
@media (max-width:860px){
  /* stacked, the four chapter blocks would repeat their label, title and alert plates:
     each of those appears once */
  .ix-chapter .ix-block:not(:first-child) .ix-label{display:none;}
  .ix-chapter .ix-block .ix-h2[aria-hidden]{display:none;}
  .ix-chapter .ix-block.is-resolved .ix-plates{display:none;}
  .ix-chapter .ix-block.is-resolved{margin-top:-14px;}
  /* one set of plates, each already carrying its resolution */
  .ix-plate .ix-res{opacity:1;transform:none;}
  .ix-providers .ix-block:not(:first-child) .ix-label,
  .ix-caps .ix-block:not(:first-child) .ix-label,
  .ix-models .ix-block:not(:first-child) .ix-label{display:inline-flex;}
}
/* one length of copy per screen: the written short version on phones, the full text above */
.is-short{display:none;}
@media (max-width:860px){
  .is-long{display:none;}
  .is-short{display:block;}
  /* a chapter on a phone: title, the short line, what you get, one alert with its answer,
     the capability names, the closing line. The rest is desktop reading. */
  .ix-station p{display:none;}
  .ix-stations{grid-template-columns:1fr 1fr;gap:8px;}
  .ix-station{padding:9px 10px;}
  .ix-plates .ix-plate:nth-child(n+2){display:none;}
  .ix-log{display:none;}
  .ix-chapter .ix-block.is-resolved .ix-state{margin-top:0;}
  .ix-list{gap:4px;} .ix-list li{font-size:14px;line-height:1.45;}
}
/* the panel sits where each scene is quiet: measured column luma says the dark region starts
   between 62 and 72 percent of the frame, so the panel starts at 64 percent, not 57 */
@media (min-width:861px){
  .ix-holo,.ix-holo.is-low{right:3%;width:min(33%,500px);}
  .ix-holo.is-left{left:3%;right:auto;}
  /* the headline column is darkened further where a scene is bright high on the left */
  .ix-col::before{background:linear-gradient(90deg,rgba(5,7,11,.9) 0%,rgba(5,7,11,.68) 55%,rgba(5,7,11,0) 100%);}
  .ix-col.is-right::before{background:linear-gradient(270deg,rgba(5,7,11,.9) 0%,rgba(5,7,11,.68) 55%,rgba(5,7,11,0) 100%);}
}

/* ---------- desktop audit fixes (2026-09-20) ---------- */
@media (min-width:861px){
  /* a story fades out as its stage unsticks, so two chapters are never lit at once and
     nothing is left cut in half under the header */
  .ix-col,.ix-holo,.ix-hero-copy{opacity:var(--seam,1);}
  .ix-statement{opacity:0;} .ix-statement.is-on{opacity:var(--seam,1);}
  .ix-figure{opacity:0;} .ix-figure.is-on{opacity:var(--seam,1);}
  .ix-hero-copy.is-late{opacity:calc(var(--seam,1) * (1 - max(0, var(--p) - .2) * 1.6));}
  /* the panel centres in the space under the header, like the copy column */
  .ix-holo{top:calc(50% + var(--header-h,92px) / 2);}
  /* a mark is a label, not a ghost: readable on its own and legible over a lit scene */
  .ix-mark{color:rgba(247,249,252,.62);opacity:.7;text-shadow:0 1px 8px rgba(5,7,11,.95),0 0 2px rgba(5,7,11,.9);}
  .ix-mark.is-on{opacity:1;}
}
/* wide screens get a wider measure and larger type instead of a 1536 page with big margins */
@media (min-width:1700px){
  .ix-h1{font-size:clamp(92px,5.4vw,124px);}
  .ix-h2{font-size:clamp(66px,3.9vw,88px);}
  .ix-state{font-size:clamp(56px,3.4vw,76px);}
  .ix-sub{font-size:21px;} .ix-p{font-size:19px;} .ix-list li{font-size:17px;}
  .ix-col{width:min(46%,760px);} .ix-holo,.ix-holo.is-low{width:min(34%,620px);}
  .ix-plate p,.ix-log-l{font-size:14px;} .ix-mark,.ix-label,.ix-plate-h,.ix-holo-t{font-size:13px;}
  .ix-proof-in,.ix-eco-band,.ix-eco-head{max-width:1480px;}
  .ix-station h3,.ix-station h4{font-size:17px;} .ix-station p{font-size:15px;}
}
/* the proof metric wraps into three lines at narrow widths and leaves a dead screen below */
.ix-proof-metric{font-size:clamp(34px,3.4vw,60px);text-wrap:balance;}
@media (min-width:861px){ .ix-proof-row{grid-template-columns:minmax(260px,1fr) 1.6fr auto;} }
.ix-proof{padding-bottom:clamp(56px,6vw,90px);}
/* the Kafka mark is dark monochrome and vanishes on a near-black card */
.ix-eco-mark img[src*="kafka"]{filter:brightness(0) invert(1) opacity(.85);}
.ix-eco-mark span{white-space:nowrap;}
/* the terminal chrome only animates once the card has arrived */
.cxo-contact-cta:not(.cxo-visible):not(.is-in) .cxo-cta-cursor,.cxo-contact-cta:not(.cxo-visible):not(.is-in) .cxo-cta-status-dot{animation-play-state:paused;}
@media (max-width:860px){ .ix-eco-mark span{white-space:normal;} }

/* ---------- readability pass (2026-09-20): text never sits on bare picture ---------- */
/* the floating scene labels pointed at nothing, drifted over lit artwork and clipped at the
   right edge; the copy and the panels carry the same information */
.ix-marks{display:none!important;}
/* every card that holds text is opaque enough to read on */
.ix-station{background:rgba(7,12,22,.92);border:1px solid rgba(150,225,255,.18);}
.ix-plate{background:rgba(6,11,20,.92);}
.ix-holo-panel,.ix-log{background:rgba(6,11,20,.93);}
.ix-list li{text-shadow:0 1px 6px rgba(5,7,11,.85);}
@media (min-width:861px){
  /* a real backdrop under the copy column, not a hint of one */
  .ix-col::before{top:-12%;bottom:-12%;left:-40%;right:-12%;
    background:linear-gradient(90deg,rgba(5,7,11,.95) 0%,rgba(5,7,11,.92) 45%,rgba(5,7,11,.72) 72%,rgba(5,7,11,0) 100%);}
  .ix-col.is-right::before{left:-12%;right:-40%;
    background:linear-gradient(270deg,rgba(5,7,11,.95) 0%,rgba(5,7,11,.92) 45%,rgba(5,7,11,.72) 72%,rgba(5,7,11,0) 100%);}
  /* the same for the hero copy and the closing statement, which sit on the scene too */
  .ix-hero-copy::before,.ix-statement::before{content:"";position:absolute;z-index:-1;
    top:-14%;bottom:-14%;left:-8%;right:-30%;pointer-events:none;
    background:linear-gradient(90deg,rgba(5,7,11,.92) 0%,rgba(5,7,11,.86) 50%,rgba(5,7,11,0) 100%);}
  .ix-hero-copy,.ix-statement{isolation:isolate;}
}
@media (max-width:860px){
  /* on a phone the scene is its own block, and the band where it meets the copy is solid */
  .ix-stage::before{background:linear-gradient(180deg,rgba(5,7,11,0) 42%,rgba(5,7,11,.72) 74%,var(--ix-base) 96%);}
}
@media (max-width:860px){
  /* the scene box was half empty below the picture: shorter, and the fade lands at its foot */
  .ix-stage::before{height:46svh;background:linear-gradient(180deg,rgba(5,7,11,0) 58%,rgba(5,7,11,.7) 84%,var(--ix-base) 100%);}
  .ix-story.is-hero .ix-stage::before{height:68svh;}
  .ix-layer,canvas.ix-frames,.ix-dimmer,.ix-vignette,.ix-atmos,.ix-glow,.ix-sub-wrap{height:46svh;}
  .ix-story.is-hero .ix-layer,.ix-story.is-hero canvas.ix-frames,.ix-story.is-hero .ix-dimmer,
  .ix-story.is-hero .ix-vignette,.ix-story.is-hero .ix-atmos,.ix-story.is-hero .ix-glow{height:68svh;}
  /* the frame corners read as stray marks at this size */
  .ix-plate::before,.ix-plate::after,.ix-holo-panel::before,.ix-holo-panel::after{display:none;}
  .ix-holo{margin-top:-8px;}
}
/* the standing figures use gradient text, which disappears over a lit scene: give them ink */
.ix-figure{padding:14px 18px;border-radius:12px;background:linear-gradient(180deg,rgba(5,7,11,.86),rgba(5,7,11,.72));
  border:1px solid rgba(150,225,255,.16);}
.ix-figure b{background:linear-gradient(100deg,#ffffff,#bfe9ff);-webkit-background-clip:text;background-clip:text;}
.ix-proof-metric{text-shadow:0 2px 14px rgba(5,7,11,.7);}
@media (max-width:860px){ .ix-figure{padding:10px 14px;} .ix-proof-row{text-align:left;} }

@media (max-width:860px){
  /* less to read on a phone: the incident plate is the longest mono block and its resolution
     is already said by the closing line */
  .ix-chapter .ix-plates{display:none;}
  .ix-stations{gap:7px;} .ix-station{padding:8px 10px;} .ix-station h3,.ix-station h4{font-size:13.5px;}
}/* an empty pod slot stays an empty slot and never lights */
.ix-cluster .pod.is-slot rect{opacity:.14;stroke-dasharray:3 4;}
/* the hero copy is gone before the closing line arrives, and a chapter's copy only lights
   once its stage has pinned, so two chapters are never legible at the same moment */
@media (min-width:861px){
  .ix-hero-copy.is-late{opacity:calc(var(--seam,1) * (1 - max(0, var(--p) - .12) * 2.6));}
}

/* ---------- phones: a still per idea, nothing moves (2026-09-20) ---------- */
/* a picture that belongs to one block, shown in the flow of the copy on small screens only */
.ix-inline{display:none;}
@media (max-width:860px){
  .ix-inline{display:block;margin:0 0 18px;border-radius:12px;overflow:hidden;
    box-shadow:0 20px 50px -30px rgba(0,0,0,.9);border:1px solid rgba(150,225,255,.14);}
  .ix-inline img{display:block;width:100%;height:auto;}
  /* the section scene holds still: no parallax, no push, no scrubbed canvas */
  .ix-layer{transform:none!important;}
  canvas.ix-frames{display:none;}
  .ix-sub-wrap{display:none;}
  /* a section that now carries its own pictures does not need a scene band as well */
  .ix-models .ix-stage::before,.ix-caps .ix-stage::before{display:none;}
  .ix-models .ix-layer,.ix-caps .ix-layer,.ix-models .ix-dimmer,.ix-caps .ix-dimmer,
  .ix-models .ix-vignette,.ix-caps .ix-vignette{display:none;}
  .ix-models .ix-col,.ix-caps .ix-col{padding-top:26px;}
}

/* ---------- panels whose body is a photographed scene ---------- */
.ix-scene-panel{padding:0;overflow:hidden;}
.ix-scene-panel .ix-holo-t{margin:0;padding:12px 16px 10px;}
.ix-scene-pic{display:block;position:relative;}
.ix-scene-pic img{display:block;width:100%;height:auto;}
.ix-scene-pic::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(6,11,20,.35) 0%,rgba(6,11,20,0) 22%,rgba(6,11,20,0) 72%,rgba(6,11,20,.55) 100%);}
.ix-steps{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 16px 14px;
  font-family:var(--font-mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--ix-dim);}
.ix-steps span{color:#cfe2f2;}
.ix-steps i{width:14px;height:1px;background:rgba(160,230,255,.35);flex:none;}
@media (min-width:1700px){ .ix-steps{font-size:12.5px;} }
@media (max-width:860px){ .ix-steps{font-size:11.5px;gap:7px;padding:10px 12px 12px;} .ix-scene-panel .ix-holo-t{padding:10px 12px 8px;} }

/* ---------- panels: glass, and half the footprint (2026-09-20) ----------
   The panels were made opaque in an earlier pass so mono text would read over a bright
   scene. That solved the contrast but turned them into dark slabs covering most of the
   artwork. Real glass does both jobs: the backdrop filter darkens and desaturates
   whatever is behind the panel IN PLACE, so light text keeps its contrast while the
   scene stays visible through it. Brightness is doing the work the opaque fill used to
   do, which is why the fill itself can go back to a tint. */
/* The grey is in the fill, not in a brightness() on the backdrop. Chrome switches a
   backdrop-filter OFF for as long as any ANCESTOR forms a backdrop root, and an element whose
   opacity is strictly between 0 and 1 is one. These cards fade in inside a wrapper that
   animates its opacity, so for the 576ms that wrapper spends below 1 the filter did nothing
   and the card rendered as its own fill: a 10% blue tint, which is light silver. Then the
   wrapper hit exactly 1 and the card snapped to graphite in a single frame. Measured at
   1600x900: luminance 151.7 flat for 576ms, then 80.4 on the next composited frame, and a
   static probe with the wrapper at opacity .999 sits at 151.4 against 80.4 at 1. That snap is
   what the owner saw as a light grey that takes a second to load.
   With the fill carrying the tone the card is dark from its first frame and fades in cleanly.
   Measured settled colour rgb(74,80,89) against rgb(78,81,83) before: the same card. */
.ix-holo-panel,.ix-log,.ix-plate,.ix-station{
  -webkit-backdrop-filter:blur(20px) saturate(125%);
  backdrop-filter:blur(20px) saturate(125%);
  background:linear-gradient(160deg,rgba(130,190,240,.10) 0%,rgba(20,55,100,.07) 55%,rgba(10,22,42,.11) 100%),rgba(10,16,27,.62);
  border:1px solid rgba(175,220,255,.24);
  box-shadow:inset 0 1px 0 rgba(215,240,255,.16),0 26px 52px -36px rgba(0,0,0,.85);
}
/* a browser without backdrop-filter gets the old opaque fill, so text never loses contrast */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .ix-holo-panel,.ix-log{background:rgba(6,11,20,.9);}
  .ix-plate{background:rgba(6,11,20,.88);}
  .ix-station{background:rgba(7,12,22,.9);}
}
/* half the footprint, so the panel stops covering the scene it is describing */
@media (min-width:861px){
  .ix-holo,.ix-holo.is-low{width:min(24%,380px);gap:10px;}
  .ix-holo-panel{padding:0;}
  .ix-plate{padding:11px 13px;}
  .ix-station{padding:10px 12px;}
}
@media (min-width:1700px){
  .ix-holo,.ix-holo.is-low{width:min(22%,440px);}
}
.ix-scene-panel .ix-holo-t{padding:9px 13px 8px;font-size:10.5px;letter-spacing:.16em;}
.ix-steps{padding:9px 13px 11px;gap:6px;font-size:11px;}
.ix-steps i{width:10px;}
.ix-log-h{padding:6px 12px;}
.ix-log-b{padding:8px 12px 10px;font-size:11.5px;line-height:1.6;}
/* phones keep the glass but at a cheaper radius: a wide blur recomputed on every scroll
   frame is the most expensive thing on a phone GPU, and 14px reads the same at this size */
@media (max-width:860px){
  .ix-holo-panel,.ix-log,.ix-plate,.ix-station{
    -webkit-backdrop-filter:blur(14px) saturate(120%);
    backdrop-filter:blur(14px) saturate(120%);
  }
  .ix-scene-panel .ix-holo-t{padding:9px 12px 7px;}
  .ix-steps{padding:8px 12px 10px;}
}
/* a gated layer is lit only for its own stretch of the story, so one section can hand over
   between several scenes; the crossfade is slow enough to read as a move through the hall */
.ix-layer.is-gated{opacity:0;transition:opacity .7s ease;}
.ix-layer.is-gated.is-on{opacity:1;}
/* with the engine off, the first gated layer stands for the section */
html:not(.ix-js) .ix-layer.is-gated,html.ix-static .ix-layer.is-gated{opacity:0;}
html:not(.ix-js) .ix-layer.is-gated:first-of-type,html.ix-static .ix-layer.is-gated:first-of-type{opacity:1;}
/* the provider bullet list sits under the tagline, small enough not to compete with it */
.ix-list.is-small{margin-top:12px;gap:5px 18px;}
.ix-list.is-small li{font-size:14px;line-height:1.45;}
@media (min-width:1700px){ .ix-list.is-small li{font-size:15.5px;} }
/* Phone pictures parallax inside their own frame. The driver is --p, the story progress
   the engine already writes on the stage and every card inherits, so there is no second
   observer to starve and no extra style write per frame: the picture just reads a number
   that is already changing. The image is taller than its frame, so it never shows a gap. */
@media (max-width:860px){
  .ix-inline{position:relative;aspect-ratio:16 / 10;}
  .ix-inline picture{position:absolute;inset:0;display:block;}
  .ix-inline img{position:absolute;left:0;top:-9%;width:100%;height:118%;object-fit:cover;
    transform:translate3d(0,calc((var(--p,.5) - .5) * -15%),0);}
}
@media (prefers-reduced-motion:reduce){
  .ix-inline img{transform:none!important;}
}

/* ---------- desktop drops the in-panel pictures (2026-09-20) ----------
   On a desktop the full scene is already playing behind the panel, so the picture inside
   the panel repeated it at a tenth of the size. The panel keeps what only it can say: its
   name and the real text of its stages. The picture moves to phones, where there is no
   full scene, and parallaxes inside its own frame there. */
@media (min-width:861px){
  .ix-scene-pic{display:none;}
  .ix-scene-panel .ix-holo-t{border-bottom:1px solid rgba(160,215,255,.16);}
}
@media (max-width:860px){
  .ix-scene-pic{position:relative;aspect-ratio:16 / 10;overflow:hidden;display:block;}
  .ix-scene-pic img{position:absolute;left:0;top:-9%;width:100%;height:118%;object-fit:cover;
    transform:translate3d(0,calc((var(--p,.5) - .5) * -15%),0);}
}
/* The opening frame of the platforms section shows all three clusters, and the copy used to
   sit on top of the left one, so AWS read as a smudge. The copy drops to the foot of the
   frame for that block only and its backdrop shrinks with it, which leaves all three
   clusters clear while the text still has something dark underneath it. */
@media (min-width:861px){
  /* the copy blocks are absolutely centred in a zero height column, so this moves the block
     itself rather than the column: aligning the column instead collapses it to the floor */
  .ix-block.is-foot{top:calc(50% + 20vh);}
  .ix-providers .ix-col:has(.ix-block.is-foot.is-on)::before{top:auto;bottom:-12%;height:62%;
    background:linear-gradient(0deg,rgba(5,7,11,.92) 0%,rgba(5,7,11,.66) 48%,rgba(5,7,11,0) 100%);}
  .ix-block.is-foot .ix-p{max-width:46ch;}
  .ix-block.is-foot .ix-h2{font-size:clamp(50px,3.1vw,68px);}
}

/* ---------- copy that belongs to the picture (2026-09-20) ----------
   The deliverables read as a plain bulleted list dropped on top of the artwork. They are
   now a ruled table: a hairline above each row, the rule and the marker picking up the
   chapter's own colour, and the whole set sitting behind a soft elliptical scrim that
   fades into the scene instead of ending at a hard edge. */
@media (min-width:861px){
  .ix-list{margin-top:22px;gap:0 30px;}
  .ix-list li{padding:11px 0 11px 22px;border-top:1px solid rgba(175,220,255,.14);line-height:1.45;}
  .ix-list li:first-child,.ix-list li:nth-child(2){border-top-color:rgba(var(--ix-k-rgb),.34);}
  .ix-list li::before{top:1.35em;width:11px;height:1px;
    background:linear-gradient(90deg,var(--ix-k),rgba(var(--ix-k-rgb),.25));box-shadow:none;}
  .ix-p{color:#c9dcea;}
  .ix-p.is-long{max-width:36ch;}
}
/* the statistic travels with its own model on a phone; the floating one is absolute and
   ungated there, so all three used to pile up in the same corner */
.ix-stat-inline{display:none;}
@media (max-width:860px){
  .ix-figure{display:none!important;}
  .ix-stat-inline{display:flex;align-items:baseline;gap:12px;margin:14px 0 4px;
    padding:12px 14px;border-radius:10px;border:1px solid rgba(var(--ix-k-rgb),.28);
    background:linear-gradient(100deg,rgba(var(--ix-k-rgb),.14),rgba(8,14,26,.30));
    -webkit-backdrop-filter:blur(12px) brightness(.5);backdrop-filter:blur(12px) brightness(.5);}
  .ix-stat-inline b{font-family:var(--font-display);font-weight:800;font-size:34px;line-height:1;
    letter-spacing:-.03em;color:#fff;}
  .ix-stat-inline span{font-family:var(--font-mono);font-size:11px;letter-spacing:.16em;
    text-transform:uppercase;color:var(--ix-dim);}
  .ix-list{gap:0;}
  .ix-list li{padding:9px 0 9px 20px;border-top:1px solid rgba(175,220,255,.13);}
}

/* ---------- the platform tabs, phones only ----------
   A pinned scene cannot work on a phone, and stacked the three platforms read as three
   headings with no pictures. The reader picks a platform and gets one picture and one set
   of facts, switched by the button rather than by scroll position. */
.ix-tabs.is-phone-only{display:none;}
@media (max-width:860px){
  .ix-tabs.is-phone-only{display:block;position:relative;z-index:4;margin:0 14px;padding-top:6px;}
  .ix-block.is-wide-only{display:none!important;}
  .ix-tabbar{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px;}
  .ix-tab{display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:7px;
    padding:11px 6px 10px;border-radius:12px;cursor:pointer;
    border:1px solid rgba(175,220,255,.16);background:rgba(8,14,26,.42);
    -webkit-backdrop-filter:blur(14px) brightness(.55);backdrop-filter:blur(14px) brightness(.55);
    font-family:var(--font-mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
    color:var(--ix-dim);transition:border-color .25s ease,color .25s ease,background .25s ease;}
  .ix-tab img{height:20px;width:auto;opacity:.85;transition:opacity .25s ease;}
  .ix-tab.is-on{border-color:rgba(var(--ix-k-rgb),.6);color:#fff;background:rgba(var(--ix-k-rgb),.12);}
  .ix-tab.is-on img{opacity:1;}
  .ix-tab:focus-visible{outline:2px solid var(--ix-k);outline-offset:2px;}
  .ix-tabpane[hidden]{display:none;}
  .ix-tabpane .ix-inline{display:block;margin:0 0 16px;border-radius:12px;overflow:hidden;
    aspect-ratio:4 / 3;position:relative;}
  .ix-tabpane .ix-h2{font-size:30px;line-height:1.08;margin:0 0 6px;}
  .ix-tabpane .ix-sub{margin:0 0 4px;}
  .ix-tabpane .ix-cta{margin-top:14px;}
}

/* The contact card sat 204px clear of the footer: 130px of section padding under it plus
   the footer's own 96px top margin, on top of the card's own 44px. This stylesheet only
   loads on the two immersive pages, so both are safe to close up here. */
.ix-console{padding-bottom:0;}
.ix-console + .cx-footer,.cx-footer{margin-top:clamp(16px,2vw,32px);}
@media (max-width:860px){ .ix-console{padding-top:clamp(44px,8vw,70px);} }

/* ---------- hero: copy lower, scroll cue that asks to be used (2026-09-20) ----------
   The copy sat on the vertical centre, which put its scrim across the top-left of the
   scene where the amber cloud lives. It drops below centre so the artwork above it is
   clear, and its scrim is an ellipse that follows the text rather than a band. */
@media (min-width:861px){
  .ix-hero-copy{top:auto;bottom:7vh;transform:none;max-width:820px;}
  .ix-hero .ix-col::before{display:none;}
  .ix-hero .ix-sub{max-width:52ch;}
}
/* the cue now reads as an instruction: a stroke that runs down the rail, an arrowhead that
   pulses with it, and the word beneath. It stops when the reader has started scrolling and
   whenever the story is off screen, so nothing animates at rest. */
.ix-cue{flex-direction:row;align-items:center;gap:13px;bottom:30px;opacity:.92;transition:opacity .4s ease;}
.ix-cue span{order:2;font-family:var(--font-mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;color:#b6cede;}
.ix-cue i{order:1;position:relative;width:25px;height:40px;border-radius:13px;background:transparent;
  border:1.5px solid rgba(175,220,255,.5);box-shadow:inset 0 0 12px rgba(125,211,252,.10);}
.ix-cue i::before{content:"";position:absolute;left:50%;top:7px;width:5px;height:5px;margin-left:-2.5px;
  border-radius:50%;background:var(--ix-cyan);box-shadow:0 0 9px rgba(125,211,252,.95);}
.ix-cue i::after{content:none;}
.ix-story.is-live .ix-cue i::before{animation:ix-cue-wheel 2s cubic-bezier(.45,0,.35,1) infinite;}
@keyframes ix-cue-wheel{0%{transform:translateY(0);opacity:0}14%{opacity:1}68%{transform:translateY(17px);opacity:1}100%{transform:translateY(21px);opacity:0}}
/* once the reader is past the first screen the cue has done its job */
.ix-hero.is-moved .ix-cue{opacity:0;}
@media (prefers-reduced-motion:reduce){ .ix-cue i::before,.ix-cue i::after{animation:none!important;} }
/* the contact card lines up with the header and the footer, which share a 1180px column */
.ix-console .cxo-contact-cta{max-width:1180px;margin-left:auto;margin-right:auto;}

/* ---------- scrim without an edge (2026-09-20) ----------
   The previous pass gave the copy scrim a box narrower than the gradient inside it, so the
   ramp had not reached transparent by the time the box ended and the box edge itself read as
   a hard vertical line across the artwork. The scrim now spans the viewport, so there is no
   edge anywhere for it to show, and it is lighter because the scenes already fall away dark
   on the copy side. */
@media (min-width:861px){
  /* A scrim with a handful of stops steps visibly where its slope changes, and that step is
     what read as a line drawn across the picture. This ramp is sampled every 8 percent and
     spans the whole viewport, so there is no edge and no step anywhere in it. */
  .ix-col::before{top:0;bottom:0;left:0;right:auto;width:100vw;background:linear-gradient(90deg,rgba(5,7,11,0.84) 0%,rgba(5,7,11,0.8) 8%,rgba(5,7,11,0.74) 16%,rgba(5,7,11,0.66) 24%,rgba(5,7,11,0.56) 32%,rgba(5,7,11,0.45) 40%,rgba(5,7,11,0.34) 48%,rgba(5,7,11,0.24) 56%,rgba(5,7,11,0.15) 64%,rgba(5,7,11,0.08) 72%,rgba(5,7,11,0.035) 80%,rgba(5,7,11,0.01) 88%,rgba(5,7,11,0) 96%);}
  .ix-col.is-right::before{left:auto;right:0;width:100vw;background:linear-gradient(270deg,rgba(5,7,11,0.84) 0%,rgba(5,7,11,0.8) 8%,rgba(5,7,11,0.74) 16%,rgba(5,7,11,0.66) 24%,rgba(5,7,11,0.56) 32%,rgba(5,7,11,0.45) 40%,rgba(5,7,11,0.34) 48%,rgba(5,7,11,0.24) 56%,rgba(5,7,11,0.15) 64%,rgba(5,7,11,0.08) 72%,rgba(5,7,11,0.035) 80%,rgba(5,7,11,0.01) 88%,rgba(5,7,11,0) 96%);}
  .ix-hero-copy::before,.ix-statement::before{top:0;bottom:0;left:0;right:auto;width:100vw;
    background:linear-gradient(90deg,rgba(5,7,11,0.84) 0%,rgba(5,7,11,0.8) 8%,rgba(5,7,11,0.74) 16%,rgba(5,7,11,0.66) 24%,rgba(5,7,11,0.56) 32%,rgba(5,7,11,0.45) 40%,rgba(5,7,11,0.34) 48%,rgba(5,7,11,0.24) 56%,rgba(5,7,11,0.15) 64%,rgba(5,7,11,0.08) 72%,rgba(5,7,11,0.035) 80%,rgba(5,7,11,0.01) 88%,rgba(5,7,11,0) 96%);}
}
/* the platform mark sits on the same row as its label, at a size that reads as a brand mark
   rather than a bullet, and vertically centred against the text beside it */
.ix-block .ix-logo{height:34px;}
@media (min-width:861px){
  .ix-logo{height:58px;margin-bottom:14px;}
  .ix-logo.is-wide{height:64px;}
  .ix-providers .ix-block{display:flex;flex-direction:column;align-items:flex-start;}
  .ix-providers .ix-logo{align-self:flex-start;}
}
@media (min-width:1700px){ .ix-logo{height:68px;} .ix-logo.is-wide{height:74px;} }
/* the cue reads at arm's length: a larger mouse, a brighter outline and a label with enough
   weight to be an instruction rather than a caption */
.ix-cue i{width:29px;height:46px;border-radius:15px;border-color:rgba(190,230,255,.72);}
.ix-cue i::before{top:8px;width:5.5px;height:5.5px;margin-left:-2.75px;}
.ix-cue span{font-size:12px;letter-spacing:.22em;color:#d2e6f4;}
@keyframes ix-cue-wheel{0%{transform:translateY(0);opacity:0}14%{opacity:1}68%{transform:translateY(20px);opacity:1}100%{transform:translateY(25px);opacity:0}}
/* the copy drifts up as the story starts, but it no longer centres itself first: the -50%
   in the .is-late transform is what kept pulling the block back to the middle and undoing
   the bottom offset, because two classes outrank one */
@media (min-width:861px){
  .ix-hero-copy.is-late{transform:translate3d(0,calc(var(--p) * -60px),0);}
}

/* ---------- phone: showcase first, then the words, then the card picture (2026-09-20) ----------
   The card picture sat above its own heading, so every section opened with a picture whose
   subject had not been named yet, and on the platforms section the full-bleed scene and the
   card underneath it were the same photograph one after the other. Ordering is done here
   rather than in the markup so the desktop DOM order, which the story engine gates on,
   does not change. */
@media (max-width:860px){
  .ix-block,.ix-tabpane{display:flex;flex-direction:column;}
  .ix-block>*,.ix-tabpane>*{order:1;}
  .ix-block>.ix-inline,.ix-tabpane>.ix-inline{order:8;margin:18px 0 0;}
  .ix-block>.ix-cta,.ix-tabpane>.ix-cta{order:9;}
  /* the platforms section already shows the whole line-up as its backdrop, so the single
     platform scenes would repeat the card picture directly above itself */
  .ix-providers .ix-layer.is-solo{display:none!important;}
  /* the gap between one card and the next was reading as a hole */
  .ix-block{margin-bottom:26px;}
  .ix-block:last-child{margin-bottom:10px;}
  .ix-col-in>.ix-block>.ix-p:last-of-type{margin-bottom:0;}
}

/* ---------- the last edge, the heading size, and the deliverables (2026-09-20) ----------
   Making the scrim span the viewport killed the vertical edge but left its top and bottom.
   The gradient runs left to right, so it has no vertical variation and can be extended far
   past the viewport in both directions: then no edge of the box is ever on screen. */
@media (min-width:861px){
  .ix-hero-copy::before,.ix-statement::before{top:-120vh;bottom:-120vh;}
  /* the headline was crowding the artwork it is supposed to sit beside */
  .ix-hero .ix-h1{font-size:clamp(46px,4.4vw,76px);line-height:1.02;}
  .ix-hero-copy{max-width:min(46vw,680px);}
  .ix-hero .ix-sub{font-size:17.5px;line-height:1.6;max-width:46ch;}
  .ix-hero .ix-label{font-size:11px;}
}
/* The deliverables were a ruled list on bare artwork, which read as a plain table. They sit
   on glass now, as one panel with hairline separators, an accent keyline down the leading
   edge and a marker that points rather than just sits. */
@media (min-width:861px){
  .ix-chapter .ix-list{margin-top:24px;gap:0 0;grid-template-columns:1fr 1fr;
    border-radius:12px;overflow:hidden;
    border:1px solid rgba(175,220,255,.18);border-left:2px solid rgba(var(--ix-k-rgb),.75);
    -webkit-backdrop-filter:blur(18px) saturate(120%) brightness(.52);
    backdrop-filter:blur(18px) saturate(120%) brightness(.52);
    background:linear-gradient(140deg,rgba(130,190,240,.09),rgba(12,24,44,.06));
    box-shadow:inset 0 1px 0 rgba(215,240,255,.14),0 24px 48px -36px rgba(0,0,0,.85);}
  .ix-chapter .ix-list li{margin:0;padding:13px 16px 13px 30px;border-top:1px solid rgba(175,220,255,.12);
    font-size:15.5px;line-height:1.4;color:#dceaf5;}
  .ix-chapter .ix-list li:nth-child(odd){border-right:1px solid rgba(175,220,255,.12);}
  .ix-chapter .ix-list li:first-child,.ix-chapter .ix-list li:nth-child(2){border-top:0;}
  .ix-chapter .ix-list li::before{content:"";left:14px;top:1.22em;width:6px;height:6px;
    border-right:1.5px solid var(--ix-k);border-top:1.5px solid var(--ix-k);
    transform:rotate(45deg);background:none;box-shadow:none;border-radius:0;}
}
@media (min-width:1700px){ .ix-chapter .ix-list li{font-size:17px;padding:15px 18px 15px 34px;} }
/* a phone keeps the band of the picture its subject is actually in */
@media (max-width:860px),(orientation:portrait){
  .ix-layer img{object-position:var(--pos-m,var(--pos,50% 50%));}
}
/* One column on a phone. Two columns of station cards on a 390px screen put every other
   card's text at x=210, which is the text that reads as shoved to the right, and left each
   card about 170px wide to hold a heading and a sentence. */
@media (max-width:860px){
  .ix-stations{grid-template-columns:1fr;gap:10px;}
  .ix-station p{display:block;}
  .ix-list{grid-template-columns:1fr;}
}
/* the room hand-over is quicker and starts from the moment the next one is due, so the walk
   reads as continuous rather than as three slides */
.ix-layer.is-gated{transition:opacity .42s cubic-bezier(.33,0,.2,1);}

/* ---------- the platforms card on a phone (2026-09-20) ----------
   The section backdrop repeated the very picture the card underneath it was about, so the
   platform appeared twice and the top copy was cropped by the stage. Phones get the card
   only. Inside it the order is buttons, picture, words. The picture is the landscape
   derivative in a landscape card so it is not cropped twice, and the platform's name is
   carried by the selected button rather than by a headline the width of the screen. */
@media (max-width:860px){
  .ix-providers .ix-layer,.ix-providers .ix-vignette,.ix-providers .ix-dimmer{display:none!important;}
  .ix-tabpane>.ix-inline{order:1;margin:0 0 15px;aspect-ratio:16 / 10;}
  .ix-tabpane>.ix-sub{order:2;}
  .ix-tabpane>.ix-list{order:3;}
  .ix-tabpane>.ix-cta{order:4;margin-top:16px;}
  .ix-sub.is-lead{font-size:17px;color:#cfe0ee;margin:0 0 2px;}
  .ix-tab img{height:24px;}
}

/* ---------- what the phone audit found (2026-09-20) ---------- */
@media (max-width:860px){
  /* 1. Hiding the platforms scene left its 46svh spacer behind, so the section opened with
     388px of pure black, 46 percent of a screen, and the "See the platforms" link landed
     the reader in the middle of it. The spacer goes with the scene it was holding room for. */
  .ix-providers .ix-stage::before{display:none;}
  /* 2. Three different left gutters inside one card: the copy at 18, the tab card at 14 and
     the list text at 34. Four pixels out reads as broken rather than deliberate. */
  .ix-tabs.is-phone-only{margin:0 18px;}
  /* 3. The card picture was 18 percent taller than the frame it is seen through and the
     parallax pushed a third of the file out of view, differently on every card. Half the
     overshoot, half the travel: the frame now shows 92 percent of the height, not 65. */
  .ix-inline img,.ix-scene-pic img{top:-4%;height:108%;
    transform:translate3d(0,calc((var(--p,.5) - .5) * -7%),0);}
  /* 4. A label that wraps strands its trailing hairline out on its own at the far right. */
  .ix-label::after{display:none;}
}
/* 5. The anchor offset was 112px against a header whose bottom edge is at 93, so every jump
   left a 19px slice of the previous section under the glass, and on one anchor that slice
   was a live 48px button that could still be tapped. */
html{scroll-padding-top:96px;}
/* 6. A portrait plate in a landscape band loses 27 percent off the top and bottom at tablet
   width. Above 600px the band is landscape enough to take the landscape source instead. */
@media (min-width:601px){
  .ix-col-in{max-width:620px;}
  .ix-list,.ix-stations{max-width:620px;}
}

/* ---------- reels: a clip per chapter, played once on arrival (2026-09-20) ---------- */
/* On a desktop the reel is the stage exactly: no parallax margin, so a 1920x1080 clip covers
   the screen's own pixels. On a phone it sets no geometry of its own and is the scene band
   the generic layer rules make of every layer, so the two cannot drift apart. It is invisible
   until its first played frame, so the plate beneath carries the stage until then and nothing
   of the clip is seen before it plays; once played, or skipped after playing, or standing in
   for reduced motion (poster loaded first), its end frame stays; a reel that never showed a
   frame stays invisible and the plate carries on. */
.ix-layer.is-reel{z-index:1;transform:none;}
@media (min-width:861px){ .ix-layer.is-reel{top:0;left:0;width:100%;height:100%;} }
.ix-story.is-live .ix-layer.is-reel{will-change:auto;}
.ix-layer.is-reel video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:var(--pos,50% 50%);display:block;opacity:0;transition:opacity .35s ease;}
.ix-layer.is-reel.is-contain video{object-fit:contain;}
.ix-layer.is-reel.is-playing video,.ix-layer.is-reel.is-done video{opacity:1;}
@media (max-width:860px), (orientation:portrait){ .ix-layer.is-reel video{object-position:var(--pos-m,var(--pos,50% 50%));} }
/* the skip filament, in the stage's corner, only while the page is held: tracked mono and a
   trailing hairline, no plate (docs/filament-label.md) */
.ix-reel-skip{position:absolute;z-index:5;right:clamp(20px,3vw,44px);bottom:clamp(22px,4vh,40px);display:inline-flex;align-items:center;gap:10px;margin:0;padding:6px 0;border:0;background:none;cursor:pointer;
  font-family:var(--font-mono);font-size:11px;font-weight:700;line-height:1;letter-spacing:.24em;text-transform:uppercase;color:#dbeefc;text-shadow:0 1px 8px rgba(6,6,14,.75);opacity:0;transition:opacity .3s ease;}
.ix-reel-skip::after{content:"";width:26px;height:1px;flex:none;margin-left:-.24em;background:linear-gradient(90deg,var(--ix-k),transparent);}
.ix-reel-skip[hidden]{display:none;}
.ix-stage.is-held .ix-reel-skip{opacity:.92;}
.ix-reel-skip:hover,.ix-reel-skip:focus-visible{color:#fff;opacity:1;}
.ix-reel-skip:focus-visible{outline:2px solid var(--ix-cyan);outline-offset:4px;}

/* ---------- the film: captions timed to the reel's clock, and the copy walk after it (2026-09-21) ----------
   Four forms of text over a playing reel, all switched by /js/reel.js from the clip's own
   time (data-t "on off" or "on end" in seconds): CARD, the intro's glass card sliding in from
   the nearer edge, overshooting and settling; FIL, the filament of docs/filament-label.md on
   artwork; TYPE, the film's own display face; STATE, the chapter's label and resolved line on
   an edgeless halo. Every transition is opacity and transform, nothing runs at rest, no
   will-change, no keyframes. */
@font-face{font-family:'Anton';font-style:normal;font-weight:400;font-display:swap;src:url('/fonts/Anton-Regular.woff2') format('woff2');}
.ix-caps{position:absolute;inset:0;z-index:3;pointer-events:none;--cap-x:clamp(24px,3vw,56px);--cap-top:calc(var(--header-h,92px) + clamp(20px,3vh,44px));--cap-bot:clamp(20px,3vh,44px);}
.ix-hero .ix-caps{--cap-top:clamp(20px,3vh,44px);}
/* the video's own 16:9 rectangle under object-fit cover, so a point in frame percent lands on the same film pixels at every viewport ratio */
.ix-frame{position:absolute;left:50%;top:50%;width:max(100%,177.78svh);height:max(100svh,56.25vw);transform:translate(-50%,-50%);pointer-events:none;}
.ix-cap{position:absolute;opacity:0;visibility:hidden;transform:translate3d(var(--ex,0px),calc(var(--ty,0%) + var(--ey,0px)),0) scale(var(--es,1)) rotate(var(--er,0deg));
  transition:opacity .16s ease,transform .16s ease,visibility 0s linear .16s;}
.ix-cap.is-on{opacity:1;visibility:visible;transform:translate3d(var(--cx,0px),var(--ty,0%),0) scale(1) rotate(var(--tilt,0deg));}
.ix-cap[data-at="tl"]{left:var(--cap-x);top:var(--cap-top);} .ix-cap[data-at="tr"]{right:var(--cap-x);top:var(--cap-top);}
.ix-cap[data-at="bl"]{left:var(--cap-x);bottom:calc(var(--cap-bot) + 96px);} .ix-cap[data-at="br"]{right:var(--cap-x);bottom:calc(var(--cap-bot) + 48px);}
.ix-cap[data-at="l"]{left:var(--cap-x);top:50%;--ty:-50%;} .ix-cap[data-at="r"]{right:var(--cap-x);top:50%;--ty:-50%;}
.ix-cap[data-at="t"]{left:50%;top:calc(var(--cap-top) + 2vh);--cx:-50%;--ex:-50%;} .ix-cap[data-at="b"]{left:50%;bottom:6vh;--cx:-50%;--ex:-50%;}
.ix-frame .ix-cap{left:var(--fx);top:var(--fy);}
/* the card: the intro's glass card, sliding in from the nearer edge, overshooting and settling */
.ix-card{--ex:-11.5vw;--es:.92;--er:-4deg;--tilt:-2deg;width:min(30vw,420px);padding:16px 20px;border-radius:18px;background:rgba(12,14,20,.52);
  border:1.5px solid rgba(255,255,255,.26);box-shadow:0 22px 50px rgba(0,0,0,.28),inset 0 1px 0 rgba(255,255,255,.34);
  -webkit-backdrop-filter:blur(16px) saturate(120%) brightness(.55);backdrop-filter:blur(16px) saturate(120%) brightness(.55);}
.ix-card[data-at="tr"],.ix-card[data-at="r"],.ix-card[data-at="br"]{--ex:11.5vw;--er:4deg;--tilt:2deg;}
.ix-card[data-at="t"],.ix-card[data-at="b"]{--ey:4vh;--er:0deg;--tilt:0deg;}
.ix-card.is-res{width:min(34vw,460px);}
.ix-card.is-on{transition:opacity .35s ease,transform 1.05s cubic-bezier(.2,1.35,.3,1),visibility 0s;}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){.ix-card{background:rgba(6,11,20,.9);}}
.ix-card-h{display:flex;justify-content:space-between;align-items:center;gap:14px;margin-bottom:8px;}
.ix-card-t{font-family:var(--font-display);font-weight:800;font-size:17px;color:#fff;display:inline-flex;align-items:center;gap:10px;}
.ix-card-t::before{content:"";width:18px;height:1px;flex:none;background:linear-gradient(90deg,transparent,var(--ix-k));}
.ix-card-tag{display:inline-flex;align-items:center;gap:10px;font-family:var(--font-mono);font-size:11px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;color:#dbeefc;white-space:nowrap;}
.ix-card-tag::after{content:"";width:18px;height:1px;flex:none;margin-left:-.22em;background:linear-gradient(90deg,var(--ix-k),transparent);}
.ix-card-tag.is-bad{--ix-k:var(--ix-red);color:#ffb3ba;}
.ix-card p{margin:0 0 4px;font-family:var(--font-mono);font-size:clamp(13px,.95vw,15px);line-height:1.55;color:#d7e8f2;opacity:.17;transform:translate3d(0,6px,0);transition:opacity .26s ease,transform .3s ease;}
.ix-card p:last-child{margin-bottom:0;}
.ix-card p.is-on{opacity:1;transform:none;} .ix-card p.is-mod{color:#f5d76e;}
.ix-card p.ix-res{font-family:var(--font-body);color:#eafff9;} .ix-card p.ix-res::before{content:"OK";display:inline-block;margin-right:10px;font-family:var(--font-mono);font-size:11px;letter-spacing:.2em;color:var(--ix-teal);}
/* the filament, on artwork */
.ix-fil{display:inline-flex;align-items:center;gap:10px;font-family:var(--font-mono);font-size:12px;font-weight:700;letter-spacing:.24em;text-transform:uppercase;white-space:nowrap;
  color:var(--fi,#dbeefc);text-shadow:0 1px 8px rgba(6,6,14,.75);--ex:-12px;}
.ix-fil::before,.ix-fil::after{content:"";width:26px;height:1px;flex:none;transform:scaleX(0);transition:transform .24s cubic-bezier(.22,.61,.36,1);}
.ix-fil::before{background:linear-gradient(90deg,transparent,var(--fl,var(--ix-k)));transform-origin:100% 50%;}
.ix-fil::after{background:linear-gradient(90deg,var(--fl,var(--ix-k)),transparent);transform-origin:0 50%;margin-left:-.24em;}
.ix-fil.is-left::after,.ix-fil.is-right::before{display:none;} .ix-fil.is-right{--ex:12px;}
.ix-fil.is-on::before,.ix-fil.is-on::after{transform:scaleX(1);}
.ix-fil.is-on{transition:opacity .3s ease var(--d,0s),transform .3s cubic-bezier(.22,.61,.36,1) var(--d,0s),visibility 0s;}
.ix-fil.is-bad{--fl:var(--ix-red);--fi:#ffb3ba;} .ix-fil.is-ok{--fl:var(--ix-teal);--fi:#c9fff2;} .ix-fil.is-idle{--fl:#6b7280;--fi:#b8bfc9;}
.ix-fil.is-ink{--fi:#0B1018;text-shadow:none;}
.ix-fil-row{display:flex;gap:22px;} .ix-fil-col{display:flex;flex-direction:column;gap:8px;align-items:flex-start;}
.ix-fil-row .ix-fil,.ix-fil-col .ix-fil{--d:calc(var(--i,0) * 60ms);}
/* a column whose items light one by one: every item stands from the start, slate until its own time */
.ix-fil-col .ix-fil{opacity:1;visibility:visible;transform:none;--fl:#6b7280;--fi:#b8bfc9;transition:color .3s ease;}
.ix-fil-col .ix-fil::before,.ix-fil-col .ix-fil::after{transform:scaleX(1);}
.ix-fil-col .ix-fil.is-on{--fl:var(--ix-teal);--fi:#c9fff2;}
/* the type: the film's own face (THE STACK in the intro is set in it) */
.ix-type{font-family:'Anton',var(--font-display);font-weight:400;text-transform:uppercase;color:#fff;line-height:.86;letter-spacing:.01em;white-space:nowrap;
  text-shadow:0 10px 34px rgba(0,0,0,.42);font-size:clamp(64px,7.7vw,148px);--ey:2.1vh;--es:.94;--er:-1.5deg;--tilt:-1.5deg;}
.ix-type.is-front{font-size:clamp(40px,4.6vw,88px);}
/* Every display title carries the same gradient the site sets its headings in (.ix-h1 .g above):
   white falling through pale blue into the brand cyan. One rule, no dark variant. A dark
   register was tried for the titles that sit on the sunlit plates and the owner rejected it as
   unreadable, so those get the light gradient too and hold against a bright wall on their
   shadows instead: a tight dark one for edge contrast, a wide one for depth, and a cyan glow.
   Clipped text cannot carry a text-shadow, so all three are drop shadows on the element. */
.ix-type,.ix-type.is-ink{background:linear-gradient(100deg,#ffffff 0%,#d9f4ff 45%,var(--ix-cyan) 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;text-shadow:none;
  filter:drop-shadow(0 1px 3px rgba(4,10,20,.75)) drop-shadow(0 10px 34px rgba(4,10,20,.55))
         drop-shadow(0 0 30px rgba(0,200,255,.24));}
.ix-type.is-on{transition:opacity .5s ease,transform 1.2s cubic-bezier(.22,.61,.36,1),visibility 0s;}
.ix-type .ln{display:block;transition:opacity .5s ease;} .ix-type .ln:not(.is-on){opacity:0;}
.ix-type[data-at="t"]{text-align:center;}
/* the state line */
.ix-state-cap{width:min(32vw,480px);--ex:-3vw;--es:.96;}
.ix-state-cap::before{content:"";position:absolute;inset:-30%;z-index:-1;background:radial-gradient(60% 60% at 30% 50%,rgba(5,7,11,.72),rgba(5,7,11,0) 72%);pointer-events:none;}
.ix-state-cap .ix-state{margin-top:12px;font-size:clamp(24px,2.6vw,40px);}
.ix-state-cap.is-on{transition:opacity .35s ease,transform .9s cubic-bezier(.2,1.35,.3,1),visibility 0s;}
/* the end state stands after any finish and gives way on the first scroll, as block 1 lights at 0.02 */
.ix-stage.is-reel-done .ix-cap.is-end{opacity:clamp(0,1 - (var(--p,0) - .02) * 40,1);visibility:visible;}
.ix-stage.is-reel-done .ix-cap.is-end.ix-type .ln{opacity:1;}
/* the dimmer under the copy walk lights with the copy, never at the release moment, so the film's last frame is seen clean */
.ix-dimmer.is-after{opacity:0;transition:opacity .6s ease;} .ix-stage:has(.ix-block.is-on) .ix-dimmer.is-after{opacity:1;}
.ix-stage.is-held .ix-cue{opacity:0!important;}
@media (min-width:861px){
  .ix-col::before{opacity:0;transition:opacity .3s ease;} .ix-col:has(.ix-block.is-on)::before{opacity:1;}
  /* the hero copy dissolves while the intro plays and returns, staggered, when the page is let go */
  .ix-stage.is-held .ix-hero-copy>*{opacity:0;transform:translate3d(0,8px,0);} .ix-stage.is-held .ix-hero-copy::before{opacity:0;}
  .ix-hero-copy>*,.ix-hero-copy::before{transition:opacity .35s ease,transform .35s ease;}
  .ix-hero-copy>*:nth-child(2){transition-delay:.06s} .ix-hero-copy>*:nth-child(3){transition-delay:.12s} .ix-hero-copy>*:nth-child(4){transition-delay:.18s}
  /* the copy blocks arrive with the card motion tamed for a reading column, their children as a transcript */
  .ix-block{transform:translate3d(-28px,calc(-50% + 8px),0) scale(.985);}
  .ix-block.is-on{transform:translate3d(0,-50%,0) scale(1);transition:opacity .26s ease .08s,transform .6s cubic-bezier(.2,1.25,.3,1) .08s,visibility 0s;}
  .ix-block .ix-tx{opacity:0;transition:opacity .45s cubic-bezier(.22,.61,.36,1);} .ix-block.is-on .ix-tx{opacity:1;transition-delay:min(calc(var(--i,0) * 60ms),360ms);}
  /* the alert plates that come back resolved in block 4 take the film card's own edge */
  .ix-chapter .ix-plate{border-radius:18px;border-color:rgba(255,255,255,.26);} .ix-chapter .ix-plate::before,.ix-chapter .ix-plate::after{display:none;}
}
/* phones: the reel plays inline in a 16:9 band at the full width of the screen (see the framing block below); the type sits over
   the band, the cards and the state stand in a fixed height rail under it, the filaments are
   dropped (the copy below says the same things), nothing is held */
@media (max-width:860px){
  .ix-caps{position:relative;inset:auto;z-index:3;min-height:112px;margin:0 18px;display:grid;}
  .ix-frame,.ix-caps .ix-fil,.ix-fil-row,.ix-fil-col{display:none;}
  .ix-caps .ix-card,.ix-caps .ix-state-cap{grid-area:1/1;position:relative;left:auto;right:auto;top:auto;bottom:auto;width:auto;--ty:0%;--ex:-24px;--er:0deg;--tilt:0deg;--es:.98;
    -webkit-backdrop-filter:none;backdrop-filter:none;background:rgba(6,11,20,.92);padding:12px 14px;}
  .ix-caps .ix-card p{font-size:12.5px;line-height:1.45;}
  .ix-caps .ix-card-h{flex-wrap:wrap;gap:4px 12px;} .ix-caps .ix-card-tag{font-size:10px;letter-spacing:.16em;}
  .ix-caps .ix-state-cap::before{display:none;} .ix-caps .ix-state-cap .ix-state{font-size:24px;}
  .ix-caps .ix-type{position:absolute;left:50%;right:auto;top:auto;bottom:calc(100% + 16px);--cx:-50%;--ex:-50%;font-size:clamp(34px,11vw,48px);white-space:normal;text-align:center;width:calc(100% - 8px);}
  .ix-caps .ix-type.is-front{font-size:clamp(24px,7vw,34px);}
  .ix-hero .ix-caps{min-height:0;margin:0;}
  .ix-stage.is-reel-done .ix-cap.is-end{opacity:1;}
  .ix-dimmer.is-after{display:none;}
  .ix-chapter .ix-block.is-resolved .ix-plates{display:grid;}
}
/* reduced motion: the end state stands on the end poster; no JavaScript: the plate carries the stage and the captions are not drawn */
html.ix-static .ix-cap.is-end{opacity:1;visibility:visible;transform:none;transition:none;}
html.ix-static .ix-cap.is-end.ix-type .ln{opacity:1;}
html:not(.ix-js) .ix-caps{display:none;}
html:not(.ix-js) .ix-dimmer.is-after,html.ix-static .ix-dimmer.is-after{display:none;}
.ix-proof-row .ix-btn{white-space:nowrap;}

/* ---------- the film's framing: the whole picture, on every screen (2026-09-21) ----------
   The reels were cover-fitted into the stage, so a viewport that is not 16:9 lost up to a
   fifth of the frame, the fixed header sat over its top, a vignette and two gradients
   darkened its edges, and a phone saw less than half its width. Now the 16:9 frame is fitted
   whole: it fills the stage only while that costs at most a tenth of the frame in one
   direction, otherwise it is shown complete with a soft field of its own colours behind it
   (a 16x9 canvas the reel engine refreshes a few times a second while the clip plays, no
   filter); the header slides away while a reel is held; the vignette is gone from reel
   stages and the stage gradient waits for the copy; phones show the full 16:9 frame at the
   full width of the screen. The plate under the clip is framed exactly as the clip is, so
   nothing jumps when it starts. */
/* Cover, and nothing but cover: the clip is always at least as wide as the screen and at least
   as tall, so it fills the stage at every window shape. There used to be a cap of 1.10 on top
   of this, and on any window wider than about 1.96 to 1 that cap came out SMALLER than the
   screen: the clip stopped short of both edges and the still underneath showed through as a
   hard vertical line about a hundred pixels in from each side. That is the breaking line. */
.ix-story.has-reel .ix-stage{--vw:max(100vw,177.78svh);}
.ix-story.has-reel .ix-vignette{display:none;}
.ix-story.has-reel .ix-stage::after{opacity:0;transition:opacity .5s ease;}
.ix-story.has-reel .ix-stage:has(.ix-block.is-on)::after,.ix-story.has-reel.is-hero .ix-stage:not(.is-held)::after{opacity:1;}
.ix-amb{position:absolute;left:-6%;top:-6%;width:112%;height:112%;display:block;opacity:0;transition:opacity .6s ease;pointer-events:none;}
/* The field stays at zero. It is 112% of the layer, so it hangs 6% past the clip on every side,
   and it is the blurred spill that showed as the vertical streaks down the sides of every shot.
   It used to be turned on here at .85 while a clip played, and because that sits further down
   the sheet than the rule meant to switch it off, it won. There is no margin for it to fill. */
@media (min-width:861px){
  .ix-story.has-reel .ix-layer.is-poster{top:0;left:0;width:100%;height:100%;transform:none;will-change:auto;}
  /* the still plate covers the stage completely, so nothing is ever bounded by a hard edge
     before a clip plays; the clip is the same picture, so the feathered clip blends into it */
  .ix-story.has-reel .ix-layer.is-poster img{position:absolute;inset:0;width:100%;height:100%;transform:none;object-fit:cover;object-position:50% 50%;}
  .ix-story.has-reel .ix-layer.is-reel video{position:absolute;inset:auto;left:50%;top:50%;
    width:var(--vw);height:auto;aspect-ratio:16/9;transform:translate(-50%,-50%);object-fit:cover;object-position:50% 50%;}
  .ix-story.has-reel .ix-frame{width:var(--vw);height:auto;aspect-ratio:16/9;}
  /* the header leaves while a reel is held, so the film has the whole screen, and returns when the page is let go */
  .cx-header{transition:transform .4s cubic-bezier(.22,.61,.36,1);}
  html.ix-reel-held .cx-header{transform:translate3d(0,-105%,0);}
}
@media (max-width:860px){
  /* Phones: the band IS the clip. The whole 16:9 frame at the full width of the screen, with
     a clean edge and nothing else. There is no margin of ambient colour and no fade: every
     version of that, however thin, left a smear of dead pixels along the edge and streaks down
     the sides, and the owner asked for a plain edge instead. The cap only exists to stop a
     landscape phone asking for a band taller than the screen. */
  .ix-story.has-reel .ix-stage,.ix-story.is-hero.has-reel .ix-stage{--band:min(60svh,56.25vw);}
  .ix-story.has-reel .ix-stage::before,.ix-story.is-hero.has-reel .ix-stage::before{height:var(--band);background:none;}
  .ix-story.has-reel .ix-layer,.ix-story.has-reel .ix-dimmer,.ix-story.is-hero.has-reel .ix-layer,.ix-story.is-hero.has-reel .ix-dimmer{
    height:var(--band);}
  /* The hero is normally pulled up under the sticky header so the picture runs behind it, and
     every stage pins at the very top of the screen. With a band rather than a full bleed frame
     that put over 100px of a 219px clip behind the bar on every chapter, cutting off his face
     and the titles. On a phone a film stage starts below the header and pins below it, so the
     whole 16:9 frame stays in the clear whether the page is at rest or scrolling. */
  /* The hero keeps its pull up under the header so the section still starts at the top of the
     page with no dead gap; the band inside it is what moves down. On a phone the stage is a
     normal block, not sticky, so this is padding rather than an offset: shifting a relative box
     with `top` would leave it overlapping the next section. */
  .ix-story.has-reel .ix-stage{padding-top:var(--header-h,92px);}
  /* The padding carries the captions and copy, which are in flow. The layers are absolute and
     take their top from the padding box, so they need the same offset spelled out, or the band
     and its captions come apart. */
  .ix-story.has-reel .ix-layer,.ix-story.has-reel .ix-dimmer{top:var(--header-h,92px);}
  /* on a phone the band is the frame, so the plate fills the band and the clip sits in it */
  .ix-story.has-reel .ix-layer.is-poster img{position:absolute;inset:0;width:100%;height:100%;transform:none;object-fit:cover;object-position:50% 50%;}
  .ix-story.has-reel .ix-layer.is-reel video{position:absolute;left:0;top:50%;width:100%;height:auto;aspect-ratio:16/9;transform:translateY(-50%);object-fit:cover;object-position:50% 50%;}
  .ix-amb{left:0;top:0;width:100%;height:100%;}
  /* The ambient field is off. It only ever existed to fill the margin round the clip, and there
     is no margin now. The mask that used to fade the band's edges is gone with it: that fade is
     what produced the dead pixels along the top and bottom and the streaks down the sides. */
  .ix-layer.is-reel.is-playing .ix-amb,.ix-layer.is-reel.is-done .ix-amb{opacity:0;}
  .ix-caps .ix-type{bottom:auto;top:calc(-56.25vw + 10px);font-size:clamp(22px,7.4vw,34px);}
  .ix-caps .ix-type.is-front{font-size:clamp(16px,5.2vw,24px);}
}

/* ---------- the resolve: the clip is drawn, not stretched (2026-09-21) ----------
   A video element that is not at its natural size is scaled by the compositor with one
   bilinear tap and no mip chain. Upscaled that goes soft; downscaled it aliases; both read
   as pixelated, which is what the owner was seeing. The canvas below carries the clip at the
   element's own device pixels, resampled by drawImage with a better filter, exactly as the
   hero globe and the frame sets in story.js do. The video element stays in the layer as the
   source of the frames and is never the thing on screen once a frame has been drawn. */
.ix-layer.is-reel canvas.ix-reelc{position:absolute;inset:0;width:100%;height:100%;display:block;opacity:0;transition:opacity .35s ease;z-index:1;}
.ix-layer.is-reel.is-painted.is-playing canvas.ix-reelc,.ix-layer.is-reel.is-painted.is-done canvas.ix-reelc{opacity:1;}
/* the video keeps the same box as the canvas so the draw is an exact fit, and it is hidden
   the moment the canvas has a frame; it is never display:none, which would stop decoding */
.ix-layer.is-reel.is-painted video{opacity:0!important;}
@media (min-width:861px){
  .ix-story.has-reel .ix-layer.is-reel canvas.ix-reelc{position:absolute;inset:auto;left:50%;top:50%;
    width:var(--vw);height:auto;aspect-ratio:16/9;transform:translate(-50%,-50%);}
}
@media (max-width:860px){
  .ix-story.has-reel .ix-layer.is-reel canvas.ix-reelc{position:absolute;left:0;top:50%;width:100%;height:auto;aspect-ratio:16/9;transform:translateY(-50%);}
}

/* ---------- phones and tablets: one service, four things (2026-09-21) ----------
   The owner's structure, in his order: the clip, one title, the table of what the service
   does, and one glass fact. Two things were wrong in the first pass. The captions that ride
   over the clip on a desktop are position:relative here, so they fell into the flow and became
   floating cards sitting between the video and the text; they are gone on these widths. And
   the 'table with 4 rows about what each service does' is the capability block, which the
   first pass hid; it is back, as four stacked rows. */
@media (max-width:1024px){
  /* The captions belong OVER the clip, as they do on a desktop. Here they were position:
     relative, so the whole set fell into the flow and landed between the video and the text as
     a stack of floating cards. They go back over the band, matching its offset and height, so
     the anchors tl / tr / bl resolve against the picture. */
  .ix-story.has-reel .ix-caps{
    position:absolute;inset:auto;
    left:0;right:0;top:var(--header-h,92px);height:var(--band);
    margin:0;min-height:0;display:block;z-index:3;pointer-events:none;}
  .ix-story.is-hero.has-reel .ix-caps{top:var(--header-h,92px);height:var(--band);}
  /* only the display titles ride over the clip here. The alert cards, filaments and state
     lines are the desktop's information layer and the page below already says all of it. */
  .ix-story.has-reel .ix-caps .ix-card,
  .ix-story.has-reel .ix-caps .ix-fil,
  .ix-story.has-reel .ix-caps .ix-fil-row,
  .ix-story.has-reel .ix-caps .ix-fil-col,
  .ix-story.has-reel .ix-caps .ix-state-cap,
  .ix-story.has-reel .ix-caps .ix-frame{display:none;}
  /* the title keeps the corner it was measured into, never the middle over his face */
  /* the older phone rule centred these with left:50% and a -50% shift; both have to be undone
     or an anchor of left or right is added to a centring offset and lands in the middle again */
  .ix-story.has-reel .ix-caps .ix-type{
    position:absolute;left:auto;right:auto;top:auto;bottom:auto;
    width:auto;max-width:56%;--cx:0;--ex:0;--ey:0;--es:1;--er:0deg;--tilt:0deg;
    font-size:clamp(20px,6.2vw,34px);line-height:1.02;text-align:left;}
  .ix-story.has-reel .ix-caps .ix-type.is-front{font-size:clamp(15px,4.6vw,24px);}
  .ix-story.has-reel .ix-caps .ix-type[data-at="tl"]{left:5%;top:7%;}
  .ix-story.has-reel .ix-caps .ix-type[data-at="tr"]{right:5%;top:7%;text-align:right;}
  .ix-story.has-reel .ix-caps .ix-type[data-at="bl"]{left:5%;bottom:8%;}
  .ix-story.has-reel .ix-caps .ix-type[data-at="br"]{right:5%;bottom:8%;text-align:right;}
  .ix-story.has-reel .ix-caps .ix-type[data-at="l"]{left:5%;top:38%;}
  .ix-story.has-reel .ix-caps .ix-type[data-at="r"]{right:5%;top:38%;text-align:right;}
  .ix-story.has-reel .ix-caps .ix-type[data-at="t"]{left:5%;top:7%;}
  .ix-story.has-reel .ix-caps .ix-type[data-at="b"]{left:5%;bottom:8%;}
  /* one title, once: the kicker rides with it and never repeats */
  .ix-chapter .ix-block:not(:first-child) .ix-label{display:none;}
  /* the summary, as a bordered block */
  .ix-chapter .ix-block:nth-child(2) .ix-p{
    margin:0;padding:15px 17px;border-radius:14px;
    border:1px solid rgba(150,200,255,.15);
    background:linear-gradient(170deg,rgba(12,20,34,.55),rgba(8,14,26,.72));}
  .ix-chapter .ix-list{display:none;}
  /* THE TABLE: four rows, one per capability, each with its line */
  .ix-chapter .ix-block:nth-child(3){display:block;}
  .ix-chapter .ix-block:nth-child(3) .ix-state{display:none;}
  .ix-chapter .ix-stations{display:grid;grid-template-columns:1fr;gap:0;margin-top:14px;
    border:1px solid rgba(150,200,255,.15);border-radius:14px;overflow:hidden;}
  .ix-chapter .ix-station{padding:13px 17px;border:0;border-radius:0;
    border-top:1px solid rgba(150,200,255,.12);background:rgba(10,16,28,.5);}
  .ix-chapter .ix-station:first-child{border-top:0;}
  .ix-chapter .ix-station h3{font-size:14.5px;}
  .ix-chapter .ix-station p{display:block;margin:4px 0 0;font-size:13px;line-height:1.45;}
  /* one glass fact, its heading and its answer, not the log above it */
  .ix-chapter .ix-log{display:none;}
  .ix-chapter .ix-block.is-resolved .ix-plates{display:grid;}
  .ix-chapter .ix-plates .ix-plate:nth-child(n+2){display:none;}
  .ix-chapter .ix-plate p:not(.ix-res){display:none;}
  .ix-chapter .ix-plate .ix-res{opacity:1;transform:none;margin:0;}
  .ix-chapter .ix-block.is-resolved>.ix-state{display:none;}
  /* the rhythm: the blocks sat far apart and left dead screen between them */
  .ix-chapter .ix-block{margin:0 0 14px;}
  .ix-chapter .ix-block.is-resolved{margin-top:0;}
  .ix-chapter .ix-col-in{padding-bottom:10px;}
  /* the clip sits ON the page, not on a black panel of its own */
  .ix-story.has-reel .ix-stage,.ix-story.has-reel .ix-chapter-in{background:transparent;}
  .ix-story.has-reel{background:transparent;}
}

/* ---------- phones: one gap, used in all three places (2026-09-21) ----------
   The owner: the kicker under a clip is "practically glued to the video", the run between the
   end of a service and the next clip is "way too much", the hero copy is glued to shot 1, and
   all three should measure the same. So there is one number here and every one of those places
   uses it.
   The big gap was the stage's own padding-top of a header height. That was put there when the
   stage was a pinned full screen frame; on a phone it is a plain block partway down a long
   page, so the header is nowhere near it and the padding was 92px of empty screen between one
   service's last line and the next clip. Only the hero still needs it: the hero section is
   pulled up UNDER the header by the same amount, so its padding is what puts the band back
   below the bar. Every other stage drops it.
   The layers are absolute and take their top from the padding box, not the content box, so
   whenever the padding changes their offset has to change with it or the band and the titles
   over it come apart. */
@media (max-width:860px){
  .ix,.ix-story.has-reel{--ix-gap:30px;}
  .ix-story.has-reel:not(.is-hero) .ix-stage{padding-top:var(--ix-gap);}
  .ix-story.has-reel:not(.is-hero) .ix-layer,
  .ix-story.has-reel:not(.is-hero) .ix-dimmer,
  .ix-story.has-reel:not(.is-hero) .ix-caps{top:var(--ix-gap);}
  /* the clip to the kicker under it */
  .ix-story.has-reel .ix-col{padding-top:var(--ix-gap);padding-bottom:0;}
  .ix-story.has-reel .ix-col-in{padding-bottom:0;}
  .ix-story.has-reel .ix-block:last-child{margin-bottom:0;}
  /* And shot 1 to the first tile of the intro. This one is a glass container, not loose copy,
     so the gap goes OUTSIDE it as a margin: padding would have moved the words down and left
     the container itself still touching the clip. Its own padding is what keeps the buttons
     off the bottom border, which the first pass zeroed and flattened them against it. */
  .ix-story.is-hero.has-reel .ix-hero-copy{margin-top:var(--ix-gap);padding:0 18px;}
}

/* ---------- the last three on the desktop film (2026-09-21) ----------
   1. CI/CD: the copy moved to the right of the frame and took a pane with it. The end frame of
      that clip is a sunlit office; measured on the frame itself, the left third of it averages
      115 of 255 and the right third 71, so the old left hand column had white type lying over
      raked sunlight on the carpet with nothing behind it. The blocks carry their own glass now,
      and because each block fades in on its own element (never through a parent's opacity) the
      backdrop filter has something to sample.
   2. The migration alert moved out of the top centre, where it sat over his head at the
      parapet, into the open sky on the right. That is a data change, in services.astro.
   3. The skip control: a glass chip with a skip-to-end glyph, a ring pulsing out of it and the
      chevrons nudging forward on the same beat, instead of an 11px word in the corner. */
/* CI/CD carries no pane. Standing on the right it is over the dark side of that room, and the
   owner read it there against the other three chapters: the type contrasts on its own and the
   glass only made this one chapter look unlike the rest. The move across is the whole fix. */


.ix-reel-skip{right:clamp(20px,3vw,44px);bottom:clamp(22px,4vh,40px);gap:11px;
  padding:12px 20px 12px 17px;border:1px solid rgba(175,220,255,.2);border-radius:999px;
  background:linear-gradient(170deg,rgba(6,12,22,.44) 0%,rgba(6,12,22,.64) 100%);
  -webkit-backdrop-filter:blur(16px) saturate(120%);backdrop-filter:blur(16px) saturate(120%);
  font-size:12.5px;letter-spacing:.2em;color:#eaf6ff;
  box-shadow:0 20px 44px -26px rgba(0,0,0,.9);
  transition:opacity .3s ease,border-color .25s ease,color .25s ease;}
.ix-reel-skip::after{display:none;}
/* the ring: one pulse out of the chip's own outline, on the accent of the chapter it belongs to */
.ix-reel-skip::before{content:"";position:absolute;inset:-1px;border-radius:inherit;
  border:1px solid var(--ix-k,#00C8FF);opacity:0;pointer-events:none;}
.ix-skip-i{width:17px;height:17px;flex:none;display:block;fill:currentColor;}
.ix-skip-t{display:block;}
.ix-stage.is-held .ix-reel-skip{opacity:1;}
.ix-stage.is-held .ix-reel-skip::before{animation:ix-skip-ring 2.4s cubic-bezier(.2,.7,.2,1) infinite;}
.ix-stage.is-held .ix-skip-i{animation:ix-skip-nudge 2.4s ease-in-out infinite;}
.ix-reel-skip:hover,.ix-reel-skip:focus-visible{color:#fff;border-color:rgba(175,220,255,.42);}
@keyframes ix-skip-ring{
  0%{opacity:.6;transform:scale(1);}
  62%{opacity:0;transform:scale(1.16);}
  100%{opacity:0;transform:scale(1.16);}
}
@keyframes ix-skip-nudge{
  0%,62%,100%{transform:translate3d(0,0,0);}
  76%{transform:translate3d(4px,0,0);}
}
@media (prefers-reduced-motion:reduce){
  .ix-stage.is-held .ix-reel-skip::before,.ix-stage.is-held .ix-skip-i{animation:none;}
}
/* the intro's first tile lands the same distance below shot 1 as every other gap: the heading
   inside it carries a top margin of its own, so it starts there rather than adding to it */
@media (max-width:860px){
  .ix-story.is-hero.has-reel .ix-hero-copy>*:first-child{margin-top:0;}
}

/* ---------- the scroll cue, at a size you can see from the chair (2026-09-21) ----------
   It was a 29x46 outline with an 11px word next to it, on the hero only. It is half again as
   large now, the stroke and the wheel are brighter, and one ships with every reel, so there is
   a cue under each clip rather than only the first. It stays hidden while a clip is holding
   the page, which is the one moment when "scroll" is the wrong instruction. */
.ix-cue{gap:14px;bottom:clamp(22px,3.4vh,34px);opacity:1;}
.ix-cue i{width:38px;height:58px;border-radius:19px;
  border:2px solid rgba(205,240,255,.9);
  box-shadow:inset 0 0 18px rgba(125,211,252,.18),0 8px 26px -12px rgba(0,0,0,.95);}
.ix-cue i::before{top:10px;width:7px;height:7px;margin-left:-3.5px;
  box-shadow:0 0 15px rgba(125,211,252,1),0 0 4px rgba(255,255,255,.9);}
.ix-cue span{font-size:13px;letter-spacing:.2em;color:#eaf6ff;
  text-shadow:0 1px 10px rgba(6,6,14,.9);}
@keyframes ix-cue-wheel{
  0%{transform:translateY(0);opacity:0}
  14%{opacity:1}
  68%{transform:translateY(26px);opacity:1}
  100%{transform:translateY(32px);opacity:0}
}

/* ---------- the chapter rail (2026-09-21) ----------
   Where the reader is in the film, and a way into any part of it. It stands in the outer
   gutter: the copy column starts at clamp(24px,6vw,96px) and the stage captions at
   clamp(24px,3vw,56px), and the rail is the width of one line of vertical mono, so at every
   desktop width it sits left of all of them and never lands on the picture's subject.
   Each chapter is a filament (docs/filament-label.md): a 1px gradient hairline and tracked
   mono, no plate, no capsule, no dot. The hairline is also the progress track, so the roadmap
   needs no furniture of its own: chapters behind the reader carry their accent, the one they
   are in runs longer and its name pulses, the ones ahead are a dim hairline.
   Phones and tablets do not get it: down there the page is a plain stack and a fixed rail
   would stand on the clip. */
.ix-rail{position:fixed;z-index:6;left:clamp(10px,1.2vw,22px);
  /* centred in the space BELOW the header, not in the window: centred in the window its top
     item slid under the bar on anything shorter than about 900px */
  top:calc(50% + var(--header-h,92px) / 2);transform:translateY(-50%);
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .45s ease,visibility 0s linear .45s;}
.ix-rail.is-in{opacity:1;visibility:visible;pointer-events:auto;transition:opacity .45s ease;}
.ix-rail-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;align-items:center;gap:22px;}
.ix-rail-list li{margin:0;display:flex;}
.ix-rail-i{display:inline-flex;align-items:center;gap:11px;
  writing-mode:vertical-rl;text-orientation:mixed;text-decoration:none;
  font-family:var(--font-mono);font-size:12.5px;font-weight:700;line-height:1;
  letter-spacing:.2em;text-transform:uppercase;
  color:rgba(206,228,243,.64);
  text-shadow:0 1px 3px rgba(4,6,12,.96),0 1px 14px rgba(4,6,12,.9);
  transition:color .35s ease;}
.ix-rail-i i{width:2px;height:26px;flex:none;border-radius:2px;
  background:linear-gradient(180deg,transparent,rgba(206,228,243,.55));
  transition:height .35s ease,background .35s ease,box-shadow .35s ease;}
.ix-rail-i:hover,.ix-rail-i:focus-visible{color:#fff;}
.ix-rail-i:hover i,.ix-rail-i:focus-visible i{background:linear-gradient(180deg,transparent,var(--ix-k,#00C8FF));}
.ix-rail-i:focus-visible{outline:2px solid var(--ix-cyan);outline-offset:6px;}
/* behind the reader */
.ix-rail-i.is-past{color:rgba(222,240,251,.86);}
.ix-rail-i.is-past i{background:var(--ix-k,#00C8FF);}
/* where the reader is: brighter, a longer filament, and the name pulsing */
.ix-rail-i.is-on{color:#fff;}
.ix-rail-i.is-on i{height:44px;background:linear-gradient(180deg,transparent,var(--ix-k,#00C8FF));
  box-shadow:0 0 12px var(--ix-k,#00C8FF);}
.ix-rail-i.is-on span{animation:ix-rail-beat 2.6s ease-in-out infinite;}
@keyframes ix-rail-beat{
  0%,100%{text-shadow:0 1px 3px rgba(4,6,12,.96),0 1px 14px rgba(4,6,12,.9);}
  50%{text-shadow:0 1px 3px rgba(4,6,12,.96),0 1px 14px rgba(4,6,12,.9),0 0 18px var(--ix-k,#00C8FF);}
}
@media (prefers-reduced-motion:reduce){ .ix-rail-i.is-on span{animation:none;} }
html:not(.ix-js) .ix-rail{display:none;}
/* short windows: the four names still have to fit between the header and the fold */
@media (max-height:900px){
  .ix-rail-list{gap:15px;}
  .ix-rail-i{font-size:10.5px;letter-spacing:.15em;gap:9px;}
  .ix-rail-i i{height:18px;} .ix-rail-i.is-on i{height:30px;}
}
@media (max-height:740px){
  .ix-rail-list{gap:12px;}
  .ix-rail-i{font-size:9.5px;letter-spacing:.12em;}
  .ix-rail-i i{height:13px;} .ix-rail-i.is-on i{height:22px;}
}
@media (max-width:1024px){ .ix-rail{display:none;} }

/* ---------- the two controls share one row (2026-09-21) ----------
   The cue sat centred at the bottom of the stage and the skip chip in the corner. They belong
   together: one row in the bottom right, centred on the same line, a clear gap between them.
   They take turns rather than stack up. The skip chip is up only while the clip is holding the
   page. The cue is up only when the clip is NOT running, which covers all three of the cases
   the owner named: before it starts, once it ends, and any time it is paused or interrupted
   (a clip that scrolls out of the band is paused by reel.js, and that counts).
   `is-reel-live` is a separate class from `is-playing` on purpose: the canvas that carries the
   picture is shown on `is-playing`, so taking that off at a pause would drop the stage back to
   the plate underneath. */
.ix-reel-ui{position:absolute;z-index:5;right:clamp(20px,3vw,44px);bottom:clamp(22px,4vh,40px);
  display:flex;align-items:center;gap:clamp(18px,2vw,34px);pointer-events:none;}
.ix-reel-ui>*{pointer-events:auto;}
.ix-reel-skip{position:static;right:auto;bottom:auto;}
.ix-cue{position:static;left:auto;right:auto;bottom:auto;transform:none;opacity:0;
  transition:opacity .4s ease;pointer-events:none;}
.ix-story.has-reel .ix-stage:not(.is-held):not(.is-reel-live) .ix-cue{opacity:1;}
.ix-stage.is-held .ix-cue{opacity:0;}

/* ---------- phones: the last clip runs straight into the case studies (2026-09-21) ----------
   Two things at the end of the page. The rule above the first case study is the proof table's
   own top border, and sitting on its own a few pixels under the clip it read as a leftover
   edge rather than as a table: the table still has its rules between the rows, it just does
   not draw one against the film. And the run from the clip to the first case study was 114px
   against the 30px every other section uses, so it takes the same measure as the rest. */
@media (max-width:860px){
  .ix-proof{padding-top:var(--ix-gap,30px);}
  .ix-proof-row:first-child{border-top:0;padding-top:0;}
  .ix-proof-row:first-child .ix-label{margin-top:0;}
}

/* ---------- phones and tablets: no container round the opening copy (2026-09-21) ----------
   The glass pane was put behind the opening copy when the clips lost their edge blur and the
   words were left lying on the picture. Stacked under the clip rather than over it there is
   nothing to read against, so the pane was only drawing a box: the owner asked for the title
   and the copy to sit on the page like everything else. The gap to the clip stays, as a margin
   above the copy rather than a container's padding. */
@media (max-width:1024px){
  .ix-story.has-reel .ix-hero-copy,
  .ix-story.has-reel .ix-statement{
    background:none;border:0;border-radius:0;box-shadow:none;
    -webkit-backdrop-filter:none;backdrop-filter:none;}
}

/* ---------- the bottom right corner belongs to the controls (2026-09-21) ----------
   The scroll cue and the skip chip share one row there, 58px tall at the cue's height, and a
   caption anchored bottom right was landing on top of them ("-44% node spend" sitting across
   the cue). Anything anchored into that corner now stands above the row: its own offset, or
   the height the controls need plus a clear gap, whichever is further up. */
@media (min-width:861px){
  .ix-caps{--cap-ui:calc(clamp(22px,4vh,40px) + 58px + 20px);}
  .ix-cap[data-at="br"]{bottom:max(calc(var(--cap-bot) + 48px),var(--cap-ui));}
  .ix-cap[data-at="b"]{bottom:max(6vh,var(--cap-ui));}
}

/* ---------- the opening copy stands in the corner, not over his face (2026-09-21) ----------
   The glass pane behind it was half the screen wide and its right edge cut across his jaw. The
   owner: smaller, no container, bottom left, clear of his face and with room from the icons and
   the info tabs in the shot.
   It is only ever read against the clip's LAST frame, because the copy's children are hidden
   while the clip holds the page (see the is-held rule), so the corner it needs is the corner the
   end frame has to keep clear. The marks in shot 1 are composited, not CSS, so that corner is
   reserved in the composite itself: scripts/make-stack-clean-timeline.mjs carries a COPY_ZONE
   and the audit there treats it as an obstacle at the last frame.
   No plate under it, per docs/filament-label.md, so legibility comes from the type's own shadow. */
@media (min-width:861px){
  .ix-story.is-hero.has-reel .ix-hero-copy{
    left:calc(var(--ix-gut) + clamp(20px,2.6vw,64px));right:auto;top:auto;
    bottom:clamp(26px,5vh,58px);
    transform:none;max-width:min(430px,calc(var(--ix-vw) * 0.29));
    padding:0;background:none;border:0;border-radius:0;box-shadow:none;
    -webkit-backdrop-filter:none;backdrop-filter:none;}
  .ix-story.is-hero.has-reel .ix-hero-copy.is-late{transform:translate3d(0,calc(var(--p) * -60px),0);}
  /* A text-shadow on this heading paints THROUGH it: the second line is a gradient clipped to
     the glyphs (.ix-h1 .g sets color:transparent and -webkit-background-clip:text), so a shadow
     drawn from the glyph shapes shows through the transparent fill and floods the gradient with
     a dark blur. drop-shadow works on the rendered result instead, so the gradient survives and
     still gets an edge against a bright frame. */
  .ix-story.is-hero.has-reel .ix-h1{font-size:clamp(26px,calc(var(--ix-vw) * 0.0215),42px);line-height:1.05;font-weight:900;
    text-shadow:none;
    filter:drop-shadow(0 2px 4px rgba(4,6,12,.95)) drop-shadow(0 3px 14px rgba(4,6,12,.85)) drop-shadow(0 0 28px rgba(4,6,12,.6));}
  /* The line under the title and the kicker above it stand on the picture like the title does,
     so they get the same treatment: brighter ink, a little more weight, and a shadow stack deep
     enough to darken the picture immediately around the glyphs. Measured on the shipped page,
     the kicker was ink 154 against a background of 141, a contrast of 1.17 to 1, which is
     almost nothing. No plate is added: the label contract forbids it and the shadow is what it
     allows instead. */
  .ix-story.is-hero.has-reel .ix-sub{font-size:15px;line-height:1.5;max-width:34ch;margin-top:12px;
    color:#f0f7ff;font-weight:500;
    text-shadow:0 1px 2px rgba(3,5,10,.98),0 2px 8px rgba(3,5,10,.92),0 0 22px rgba(3,5,10,.78);}
  .ix-story.is-hero.has-reel .ix-label{color:#cdeeff;
    text-shadow:0 1px 2px rgba(3,5,10,.98),0 2px 9px rgba(3,5,10,.94),0 0 20px rgba(3,5,10,.8);}
  .ix-story.is-hero.has-reel .ix-label::before,
  .ix-story.is-hero.has-reel .ix-label::after{
    filter:drop-shadow(0 1px 2px rgba(3,5,10,.95)) drop-shadow(0 0 6px rgba(3,5,10,.8));}
  .ix-story.is-hero.has-reel .ix-meta{margin-top:12px;font-size:10px;}
  .ix-story.is-hero.has-reel .ix-cta{margin-top:16px;gap:10px;}
  .ix-story.is-hero.has-reel .ix-cta .ix-btn{padding:11px 16px;font-size:13.5px;}
}

/* ---------- phones and tablets: the page reads down the middle (2026-09-21) ----------
   Everything in the flow is centred: the kickers, the headings, the copy, the tables, the
   buttons, the case studies. The labels get their second hairline back with it, which is what
   docs/filament-label.md asks for a centred label: lead-in only when it is left aligned, both
   when it sits in the middle. The hairline was switched off down here because a label that
   wraps strands it out on its own at the far right; the row is nowrap now, so the text wraps
   inside it and the two hairlines stay either side of the block.
   The titles that ride over the clip keep their corner anchors: the owner put them there so
   they stand on the empty part of the picture rather than over his face. */
@media (max-width:1024px){
  .ix-story.has-reel .ix-col,
  .ix-story.has-reel .ix-col-in,
  .ix-story.has-reel .ix-block,
  .ix-story.is-hero.has-reel .ix-hero-copy,
  .ix-story.has-reel .ix-statement,
  .ix-chapter .ix-p,
  .ix-chapter .ix-station,
  .ix-chapter .ix-station h3,
  .ix-chapter .ix-station p,
  .ix-chapter .ix-plate,
  .ix-chapter .ix-plate .ix-res,
  .ix-proof-in,
  .ix-proof-row{text-align:center;}
  .ix-story.has-reel .ix-cta,
  .ix-story.has-reel .ix-meta{justify-content:center;}
  .ix-proof-row{justify-items:center;}
  .ix-label{justify-content:center;flex-wrap:nowrap;max-width:100%;}
  .ix-label::before,.ix-label::after{display:block;}
  .ix-label.is-left::before{display:block;}
}

/* any text block with a width of its own centres that block too, or the words sit centred
   inside a box that is itself off to the left; and the closing card follows the same rule.
   The terminal transcript inside it stays left aligned: it is a shell readout, and a centred
   one reads as broken rather than as centred. */
@media (max-width:1024px){
  .ix-story.has-reel .ix-sub,
  .ix-story.has-reel .ix-p,
  .ix-story.has-reel .ix-state,
  .ix-proof-text{margin-left:auto;margin-right:auto;}
  .cxo-contact-cta,.cxo-cta-content,.cxo-cta-title,.cxo-cta-desc{text-align:center;}
  .cxo-cta-content .cxo-kicker{justify-content:center;}
  .cxo-cta-actions,.cxo-cta-buttons{justify-content:center;}
  .cxo-cta-terminal,.cxo-cta-terminal *{text-align:left;}
}

/* the blocks are flex columns, so centring their children is what actually moves the boxes
   that carry a width of their own; text-align only centres the words inside them. And the
   opening kicker is the longest label on the page: with a hairline at each end it has to be
   told to fit a narrow phone rather than run to both edges. */
@media (max-width:1024px){
  .ix-story.has-reel .ix-block,
  .ix-story.has-reel .ix-col-in,
  .ix-story.is-hero.has-reel .ix-hero-copy{align-items:center;}
}
@media (max-width:420px){
  .ix-hero .ix-label{font-size:9.5px;letter-spacing:.14em;gap:8px;}
  .ix-hero .ix-label::before,.ix-hero .ix-label::after{width:16px;}
}

/* The opening line takes its short form on a desktop too. Standing in the corner rather than
   across the screen, the long one ran to five lines and drove the block 44px taller, and every
   pixel of that height is a pixel of the picture the marks have to keep clear of. Same words,
   the ones already written for the narrow layout. */
@media (min-width:861px){
  .ix-story.is-hero.has-reel .ix-sub.is-long{display:none;}
  .ix-story.is-hero.has-reel .ix-sub.is-short{display:block;}
}

/* ---------- the opening copy belongs to the opening of the clip (2026-09-21) ----------
   The owner: "show it only once at the beginning and then let it fade when the icons of the
   tech stack start flowing in, just show the main title and info for the first 3 or 4 seconds".
   So the copy stands while he is still down the room and leaves as the stack arrives, and the
   whole question of it landing on his face or on a mark at the end of the shot goes away.
   It rides the clip's own clock: reel.js turns on every [data-t] element in the stage at its
   time, and the copy carries data-t="0 3.6". Before the clip starts it stands as it always
   did, so a reader who has not scrolled yet still gets the page's opening. */
@media (min-width:861px){
  .ix-story.is-hero.has-reel .ix-stage.is-held .ix-hero-copy.is-on>*{opacity:1;transform:none;}
  .ix-story.is-hero.has-reel .ix-stage.is-reel-done .ix-hero-copy{opacity:0;pointer-events:none;}
  .ix-story.is-hero.has-reel .ix-hero-copy{transition:opacity .5s ease;}
}

/* The one button now stands on the picture rather than on a pane, and the old fill was a 28
   percent tint that washed out to nothing over a sunlit atrium. It carries its own colour here,
   still the brand's teal to cyan, and a shadow so it sits off the frame rather than in it. */
@media (min-width:861px){
  /* the site button, exactly as the contact form draws it (.cxo-btn in src/styles/site.css):
     green through teal into cyan, with the dark ink that ramp is designed for. The two stop
     version invented here read as a different product. */
  .ix-story.is-hero.has-reel .ix-btn.is-primary{
    background:linear-gradient(135deg,rgba(52,211,153,.92) 0%,rgba(13,148,136,.86) 52%,rgba(34,211,238,.86) 100%);
    border:1px solid rgba(125,231,196,.44);color:#04130d;
    box-shadow:0 12px 28px rgba(13,148,136,.42),inset 0 1px 0 rgba(220,255,245,.34);}
  .ix-story.is-hero.has-reel .ix-btn.is-ghost{
    background:rgba(6,11,20,.55);border-color:rgba(175,220,255,.34);color:#eaf6ff;
    -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);}
}

/* ---------- no seam gradients over a clip (2026-09-21) ----------
   Every story carries an 18vh gradient at its top and its bottom, from the page colour to
   transparent, to blend one full bleed scene into the next. A reel story does not need it and
   cannot afford it: the clip has a clean edge by design and sits on flat page background with a
   measured gap either side, so the gradient has nothing to blend and only smears the picture.
   On the last clip it is at its worst, because that story ends at the band: the bottom gradient
   lands squarely on the clip and fades 152px of it to black. That is the blur on the bottom
   border. Desktop, tablet and phone. */
.ix-story.has-reel::before,.ix-story.has-reel::after{display:none;}

/* ---------- the whole frame, not a crop of it (2026-09-21) ----------
   The clip was fitted to COVER the stage, so any window that is not 16:9 lost the difference:
   on a 1920x900 viewport that is 180px off the height, and the top of his head and the marks
   along the top edge went with it. It is fitted whole now, at most the width of the viewport
   and at most 16:9 of its height, centred, with the page's own colour either side of it.
   No ambient field in that margin and no fade at the join: both of those were tried and both
   read as a smear along the edge. The plate under the clip takes the same box, so nothing
   jumps when the clip starts, and the captions take it too, so a title anchored to a corner
   lands on the picture rather than out on the margin. */
@media (min-width:861px){
  .ix-story.has-reel .ix-stage{--vw:min(100vw,177.78svh);}
  .ix-story.has-reel .ix-layer.is-poster img{position:absolute;inset:auto;left:50%;top:50%;
    width:var(--vw);height:auto;aspect-ratio:16/9;transform:translate(-50%,-50%);object-fit:fill;}
  .ix-story.has-reel .ix-caps{left:50%;top:50%;right:auto;bottom:auto;
    width:var(--vw);height:auto;aspect-ratio:16/9;transform:translate(-50%,-50%);}
}

/* ---------- the gutters become furniture (2026-09-21) ----------
   A 16:9 clip on a window that is not 16:9 leaves a margin down each side: 73px at 2255x1187,
   none at all on a true 16:9 screen. Rather than leave that as black bars, the film's own
   chrome moves into it. The left gutter carries the chapter rail, much larger than it was; the
   right carries the two reel controls and a progress wave for the whole page.
   --ix-gut is that margin, computed the same way the clip is fitted. Where it is too narrow to
   hold anything the furniture falls back to where it used to live, and the progress wave fades
   out entirely rather than sitting on the picture. */
:root{
  --ix-vw:min(100vw,177.78svh);
  --ix-gut:max(0px,(100vw - min(100vw,177.78svh)) / 2);
  /* 0 when the gutter is under 40px, 1 once it is over about 64px */
  /* This has to be a switch, not a ramp. Anywhere in the middle of a ramp the furniture has an
     outer edge in the margin and an inner edge on the picture, which is precisely the straddling
     the owner reported: at a 50px gutter the rail ran 41 to 91 across a boundary at 50. Below
     56px nothing uses the margin and everything sits inside the picture; above it the margin is
     wide enough to hold the controls at their 44px minimum with room to spare. */
  --ix-gut-on:clamp(0,calc((var(--ix-gut) - 56px) / 1px),1);
}

@media (min-width:861px){
  /* the rail sits in the left gutter and grows with it */
  /* With a gutter the rail fills it and its outer edge is the frame edge. Without one it moves
     INSIDE the picture by a clear inset. The interpolation carries the gutter itself, not just
     the inset: at a 21px gutter an 18px inset left the rail astride the boundary. */
  .ix-rail{left:calc((1 - var(--ix-gut-on)) * (var(--ix-gut) + clamp(10px,1.2vw,22px)));
    width:max(var(--ix-gut),30px);display:flex;justify-content:center;}
  /* One size drives the whole rail, and it is the SMALLER of what the gutter can hold and what
     the screen height can hold. Sizing it off the gutter alone put a 1144px rail in an 1187px
     window and a 1483px one at 3440x1440. The height term is the usable column (the screen less
     the header and a margin) divided by the rail's own ratio of total height to type size,
     measured at about 50 with everything below scaled from the same number. */
  /* the divisor is measured, not guessed: across five window shapes the rail comes out at
     almost exactly 60 times its type size, and it has to fit the screen less the header with a
     margin to spare. */
  .ix-rail{--f:clamp(10px,min(calc(var(--ix-gut) * 0.208),calc((100svh - 132px) / 75)),17.6px);}
  .ix-rail-list{gap:calc(var(--f) * 1.1);}
  .ix-rail-i{font-size:var(--f);letter-spacing:.2em;gap:calc(var(--f) * 0.62);}
  .ix-rail-i i{width:clamp(2px,calc(var(--f) * 0.14),4px);height:calc(var(--f) * 1.5);}
  .ix-rail-i.is-on i{height:calc(var(--f) * 2.6);}

  /* the two controls stack in the right gutter instead of lying across the picture edge */
  /* --ix-uiw is the width the two controls may use: the gutter when there is one, never less
     than a readable 44px and never more than 76px. Every size inside them is a fraction of it,
     so a narrow window gets small controls rather than the same controls hanging over the
     picture. And as the gutter closes they travel back to the corner they used to occupy:
     --ix-gut-on runs 0 to 1 across that range, so right goes from the old inset to zero. */
  .ix-reel-ui{--w:clamp(44px,var(--ix-gut),76px);
    right:calc((1 - var(--ix-gut-on)) * (var(--ix-gut) + clamp(20px,3vw,44px)));
    width:var(--w);bottom:clamp(22px,4vh,40px);
    flex-direction:column-reverse;align-items:center;gap:calc(var(--w) * 0.2);}
  .ix-reel-skip{flex-direction:column;gap:calc(var(--w) * 0.09);
    padding:calc(var(--w) * 0.15) calc(var(--w) * 0.11) calc(var(--w) * 0.12);
    border-radius:calc(var(--w) * 0.22);}
  .ix-skip-i{width:calc(var(--w) * 0.26);height:calc(var(--w) * 0.26);}
  .ix-skip-t{display:block;font-size:clamp(7.5px,calc(var(--w) * 0.135),10px);letter-spacing:.1em;}
  .ix-cue{flex-direction:column;gap:calc(var(--w) * 0.12);}
  .ix-cue span{order:2;font-size:clamp(7.5px,calc(var(--w) * 0.13),10px);letter-spacing:.09em;}
  .ix-cue i{order:1;width:clamp(20px,calc(var(--w) * 0.44),34px);
    height:clamp(30px,calc(var(--w) * 0.68),52px);border-radius:calc(var(--w) * 0.24);}
  .ix-cue i::before{top:calc(var(--w) * 0.11);width:calc(var(--w) * 0.085);height:calc(var(--w) * 0.085);
    margin-left:calc(var(--w) * -0.0425);}

  /* the progress wave */
  /* Anchored below the header rather than centred: centred, its lower end met the controls at
     the foot of the same column on a short window. Shorter too, so there is clear air between
     the last dash and the scroll cue. */
  .ix-prog{position:fixed;z-index:6;right:0;
    top:calc(var(--header-h,92px) + clamp(16px,3vh,40px));
    width:var(--ix-gut);height:min(48svh,420px);
    display:flex;align-items:center;justify-content:center;gap:10px;
    pointer-events:none;opacity:var(--ix-gut-on);transition:opacity .4s ease;}
  .ix-prog-dashes{display:flex;flex-direction:column;justify-content:space-between;
    height:100%;width:100%;padding-right:clamp(8px,calc(var(--ix-gut) * 0.16),18px);}
  .ix-prog-dashes i{display:block;height:2px;border-radius:2px;margin-left:auto;
    width:clamp(12px,calc(var(--ix-gut) * 0.34),26px);
    background:linear-gradient(90deg,transparent,rgba(190,228,250,.92));
    transform-origin:100% 50%;transform:scaleX(.16);opacity:.3;
    transition:none;}
  .ix-prog-pct{position:absolute;top:0;transform:translateY(-50%);
    right:calc(clamp(8px,calc(var(--ix-gut) * 0.16),18px) + clamp(12px,calc(var(--ix-gut) * 0.34),26px) + 7px);
    font-family:var(--font-mono);font-size:clamp(9.5px,calc(var(--ix-gut) * 0.14),13px);
    font-weight:700;letter-spacing:.08em;color:#eaf6ff;
    text-shadow:0 1px 6px rgba(4,6,12,.95),0 0 12px rgba(4,6,12,.8);}
}
@media (max-width:860px){ .ix-prog{display:none;} }
html:not(.ix-js) .ix-prog{display:none;}

/* ---------- the margins are glass, not voids (2026-09-22) ----------
   Where the 16:9 clip leaves a margin down each side, that margin was the page's flat colour and
   read as a black bar. It is a glass column now: a real backdrop blur with a slightly heavier
   fill than the cards use, and a hairline along the inner edge so it reads as a panel the film
   sits between rather than as nothing.
   They follow the rail, which /js/rail.js raises with the first chapter and drops with the last,
   so no fixed panel is ever laid over the rest of the page. They sit under every piece of the
   film's furniture and take no pointer events. */
@media (min-width:861px){
  /* The same glass as the panels in the film, from the rule near the foot of this file: a light
     blue tint layer OVER a dark fill. The tint is the part that lifts the column off the page
     colour; without it the panel measured 0.5 luminance away from the black it replaced. The
     base is .72 rather than .62 for the heavier opacity he asked for. */
  .ix-gut{position:fixed;top:0;bottom:0;width:var(--ix-gut);z-index:1;pointer-events:none;
    opacity:0;visibility:hidden;transition:opacity .45s ease,visibility 0s linear .45s;
    -webkit-backdrop-filter:blur(20px) saturate(125%);backdrop-filter:blur(20px) saturate(125%);
    background:rgba(10,16,27,.72);}
  /* hairline and inner sheen on the edge that faces the picture, the same tokens the panels use */
  .ix-gut.is-l{left:0;border-right:1px solid rgba(175,220,255,.24);
    box-shadow:inset -1px 0 0 rgba(215,240,255,.10);
    background-image:linear-gradient(90deg,rgba(10,22,42,.12) 0%,rgba(20,55,100,.10) 42%,rgba(130,190,240,.15) 100%);}
  .ix-gut.is-r{right:0;border-left:1px solid rgba(175,220,255,.24);
    box-shadow:inset 1px 0 0 rgba(215,240,255,.10);
    background-image:linear-gradient(270deg,rgba(10,22,42,.12) 0%,rgba(20,55,100,.10) 42%,rgba(130,190,240,.15) 100%);}
  .ix-rail.is-in ~ .ix-gut{opacity:var(--ix-gut-on);visibility:visible;transition:opacity .45s ease;}
}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .ix-gut{background:linear-gradient(180deg,rgba(28,40,60,.96) 0%,rgba(16,24,40,.98) 100%);}
}
@media (max-width:860px){ .ix-gut{display:none;} }
html:not(.ix-js) .ix-gut{display:none;}

/* ---------- one frame round the film (2026-09-22) ----------
   The owner, on a desktop: the columns either side of the picture are to be the navbar's own
   glass with no line where the two meet, so the bar and the columns read as one piece, an
   inverted U with the film standing in it; the rail, the skip chip and the cue stand on that
   glass beside every clip, the opening and the closing one included, clear of each other and
   of the bar; and no text is ever cut off by the columns.
   Every desktop window has the frame now. The picture is fitted whole inside the window less a
   column of at least clamp(64px,4vw,96px) each side (--ix-pw), so a 16:9 screen gets its
   columns too instead of furniture lying on the picture. Height to spare goes under the navbar
   first, where the bar hides it, and whatever is left splits into a strip above and one below.
   Columns and strips are one colour: the navbar's two tint layers at their lower stops
   (src/styles/site.css, .cx-header and its scrolled ::before) over one flat neutral plate, and
   the bar sits over the same plate, so where the bar meets a column the pixels either side are
   the same. The plate is grey on purpose: the bar's backdrop filter saturates what it frosts,
   and a grey stays grey. The tint starts just under the bar's bottom edge and slides up with it
   when a reel holds the page. No backdrop filter here: behind the frame there is only the stage
   colour, so a blur would cost a layer and show nothing. While the film is up the bar's lower
   rules and its drop glow stop at the columns: they are the edge between the bar and the
   picture, not between the bar and its own frame.
   Every text layer is placed from the picture's edge, never from the screen's, and the frame's
   furniture (rail, progress wave, controls) lives at the root above the frame, not inside a
   stage beneath it. The frame and its furniture are up from the first paint of the opening
   clip. When the closing clip scrolls away the columns and strips go up with its picture, on
   the compositor, so they end where that picture ends and never stand beside the case studies,
   while their glass stays put behind the bar; the furniture fades as they start to go.
   A desktop only: a window 1025px or wider, driven by a mouse or a trackpad, with motion on.
   On a phone, on a tablet in either orientation and with reduced motion every rule here is a
   no-op and the page is as it was. The same query gates /js/rail.js, /js/reel.js and the
   scrollbar probe in services.astro: change all four together. */
:root{--ix-sb:0px;}
.ix-gut.is-t,.ix-gut.is-b,.ix-ctl{display:none;}
@keyframes ix-frame-lift{from{translate:0 0;}to{translate:0 -100vh;}}
@keyframes ix-frame-stay{from{translate:0 0;}to{translate:0 100vh;}}
@keyframes ix-wave-lift{from{translate:0 0;}to{translate:0 calc(var(--ix-w0) - 100vh);}}
@keyframes ix-wave-stay{from{translate:0 0;}to{translate:0 calc(100vh - var(--ix-w0));}}
@media (min-width:1025px) and (hover:hover) and (pointer:fine) and (prefers-reduced-motion:no-preference){
  :root{
    /* 100vw counts a classic scrollbar and the page does not; --ix-sb is that scrollbar's width,
       written before the film is parsed (services.astro) and kept current by /js/rail.js */
    --ix-cw:calc(100vw - var(--ix-sb));
    --ix-pw:min(calc(var(--ix-cw) - 2 * clamp(64px,4vw,96px)),177.78svh);
    --ix-gut:calc((var(--ix-cw) - var(--ix-pw)) / 2);
    --ix-gut-on:1;
    /* the bar's bottom edge while the film is up: its 92px, a whole number of device pixels at
       100, 125, 150, 175 and 200% scaling (see the bar's rule below) */
    --ix-hb:var(--header-h,92px);
    --ix-slack:max(0px,100svh - var(--ix-pw) * .5625);
    --ix-pbot:calc(max(0px,var(--ix-slack) - var(--ix-hb)) / 2);
    --ix-ptop:calc(min(var(--ix-slack),var(--ix-hb)) + var(--ix-pbot));
    --ix-pdy:calc(min(var(--ix-slack),var(--ix-hb)) / 2);
    --ix-clr:clamp(20px,3vh,40px);
    /* the width of the film's pair of controls; the pair stands about twice that tall */
    --ix-ctlw:clamp(44px,calc(var(--ix-gut) - 24px),76px);
    /* the margin the page had beside the picture before the frame, the scrollbar counted: the
       pair and the wave's number were sized from it then and are never drawn smaller now */
    --ix-lgut:max(0px,(100vw - min(100vw,177.78svh)) / 2);
    --ix-ctlv:max(var(--ix-ctlw),clamp(44px,var(--ix-lgut),76px));
    /* the progress wave's height, and how far the closing clip has to leave before its rising
       foot reaches the wave's lower edge (16px under it, where the wave's box stops clipping) */
    --ix-progh:min(48svh,420px,calc(100svh - var(--ix-hb) - 2 * var(--ix-clr) - 56px - 2 * var(--ix-ctlv)));
    --ix-w0:calc(100vh - var(--ix-hb) - var(--ix-clr) - 8px - var(--ix-progh) - 16px);
    --ix-plate:rgb(98,98,98);
    /* three times the bar's 22px blur: how far the bar reaches sideways for what it frosts */
    --ix-ear:66px;
    /* where the plate gives way to the tint: 2px up under the bar's bottom edge, at rest and with
       the bar gone (it leaves by 105% of its height). With the two edges on one line, a screen
       that puts that line inside a device pixel (125% and 150% scaling, and every frame of the
       bar's slide) blends the bar, the tint and the plate into that row and draws a light line
       across the columns. Under the bar's 22px blur the 2px of tint does not show. */
    --ix-seam:calc(var(--ix-hb) - 2px);
    /* While a reel holds the page the bar leaves by 100px, not by 105% of itself: 100px is a
       whole number of device pixels at 80, 90, 100, 110, 120, 125, 150, 175 and 200% scaling,
       so /js/rail.js can step the slide on whole device pixels (--ix-ease). A bar edge that
       stops across a device pixel mixes its tint into that row without the glass under it and
       draws a dark line over the columns, and a free slide stops across one in every frame. */
    --ix-seam-up:calc(var(--ix-seam) - 100px);
  }
  /* The bar is fixed here rather than sticky. It looks and behaves the same (a sticky bar at
     the top of the page is always at the top), but a sticky one is placed from the scroll
     offset, which at 125% scaling left its bottom edge a fraction of a device pixel off at some
     scroll positions and drew a dark row across the columns; a fixed one stays on whole device
     pixels. Out of the flow it no longer pushes the page down, so the hero, which was pulled up
     under it by its height, takes a 1px margin instead: in the flow the bar was 93px with its
     border, so the hero started at 1 and every section under it keeps the offset it had. Its
     slide when a reel holds the page is stepped on whole device pixels (--ix-seam-up, above). */
  html.ix-js .cx-header{position:fixed;top:0;left:0;right:0;transition-timing-function:var(--ix-ease,cubic-bezier(.22,.61,.36,1));}
  html.ix-reel-held .cx-header{transform:translate3d(0,-100px,0);}
  html.ix-js .ix-story.is-hero{margin-top:1px;}
  /* site.css now fixes the bar on every page and pads the body by its height instead; the
     film frame already accounts for the bar being out of the flow, so no padding here */
  html.ix-js body{padding-top:0;}
  /* the picture, its plate and its captions in that box; the stage round it is the frame's own
     colour (the plate under both tint layers, as the frame reads once the bar is scrolled), so
     the gap between two clips passing through the frame at a hand over is part of the frame */
  .ix-story.has-reel .ix-stage{--vw:var(--ix-pw);background:rgb(25,25,30);transition:background-color .3s ease;}
  /* and the lighter one the frame has before the bar has scrolled, so at 125% or 150% scaling
     the part of a device pixel the picture leaves at its edge is frame, not a darker line */
  .cx-header:not(.scrolled) ~ main .ix-story.has-reel .ix-stage{background:rgb(43,42,48);}
  .ix-story.has-reel .ix-layer.is-poster img,
  .ix-story.has-reel .ix-layer.is-reel video,
  .ix-story.has-reel .ix-layer.is-reel canvas.ix-reelc,
  .ix-story.has-reel .ix-caps{top:calc(50% + var(--ix-pdy));}

  /* The frame. Each piece is a clip box with two layers in it: ::before the plate, ::after the
     tint. The box is what goes up with the closing picture (below); the layers in the columns
     and the top strip are held where they are on the screen at the same time, so only the
     box's foot moves and the glass behind the bar never changes under it. */
  .ix-gut,.ix-gut.is-l,.ix-gut.is-r{border:0;box-shadow:none;background:none;overflow:hidden;
    -webkit-backdrop-filter:none;backdrop-filter:none;translate:0 calc(-1 * var(--ix-lift,0px));}
  .ix-gut.is-t,.ix-gut.is-b{display:block;left:var(--ix-gut);right:var(--ix-gut);width:auto;}
  .ix-gut.is-t{top:0;bottom:auto;height:var(--ix-ptop);}
  .ix-gut.is-b{top:auto;bottom:0;height:var(--ix-pbot);}
  .ix-rail.is-film ~ .ix-gut{opacity:1;visibility:visible;transition:opacity .45s ease;}
  .ix-gut::before,.ix-gut::after{content:"";position:absolute;left:0;right:0;top:0;pointer-events:none;
    translate:0 var(--ix-lift,0px);}
  .ix-gut::before{height:100%;background:var(--ix-plate);}
  /* The bar frosts what lies behind it, and a column meets a bright picture at its inner edge,
     so the bar's corner over the column came out lighter than the column under it. Each column's
     plate reaches --ix-ear further in along the band behind the bar, so the bar frosts flat plate
     all the way across the column's width; the ear is clipped to the bar's height and follows the
     bar up when a reel holds the page, so it is never seen on the picture. */
  /* The column's plate is the box's own background, a flat band the column's width and the
     box's full height, not cut out with a clip-path: a clip-path on this composited layer is
     drawn as a mask whose edges blur at 125% scaling, which darkened the column's first and last
     device pixel. Being flat and filling the box, it needs no holding when the box goes up. The
     ear is ::before, a band along the top under the bar that slides up with the bar when a reel
     holds the page. It moves by a transform timed like the bar's, so both run on the compositor
     and a busy main thread at a hold's start cannot leave the ear behind, bare under the bar. */
  /* the columns lie over the strips, so the strip under the closing picture, going up under the
     bar, never covers an ear */
  .ix-gut.is-l,.ix-gut.is-r{width:calc(var(--ix-gut) + var(--ix-ear));z-index:2;
    background:linear-gradient(var(--ix-plate),var(--ix-plate)) 0 0/var(--ix-gut) 100% no-repeat;}
  .ix-gut.is-r{background-position:100% 0;}
  .ix-gut.is-l::before,.ix-gut.is-r::before{height:var(--ix-seam);
    transition:transform .4s var(--ix-ease,cubic-bezier(.22,.61,.36,1));}
  html.ix-reel-held .ix-gut.is-l::before,html.ix-reel-held .ix-gut.is-r::before{
    transform:translateY(calc(var(--ix-seam-up) - var(--ix-seam)));}
  /* The tint is the navbar's two tint layers at their lower stops as one: its dark fill alone at
     the top of the page, and the scrolled layer over it once the bar has scrolled, composed
     (.62 and .474 over each other are .80012 of this colour). It starts at --ix-seam, just under
     the bar's bottom edge, and covers the column only, not its ear. */
  .ix-gut::after{height:calc(100% + var(--ix-hb) + 8px);transform:translateY(var(--ix-seam));
    background:rgba(10,8,18,.62);transition:transform .4s var(--ix-ease,cubic-bezier(.22,.61,.36,1)),background-color .3s ease;}
  .ix-gut.is-l::after{right:var(--ix-ear);}
  .ix-gut.is-r::after{left:var(--ix-ear);}
  .cx-header.scrolled ~ main .ix-gut::after{background:rgba(7.038,6.223,13.261,.80012);}
  /* the bar leaves by 100px, so its bottom edge ends 8px above the screen */
  html.ix-reel-held .ix-gut::after{transform:translateY(var(--ix-seam-up));}
  /* the strip under the picture is all tint and goes up with the picture as it is */
  .ix-gut.is-b::before,.ix-gut.is-b::after{translate:none;}
  .ix-gut.is-b::after,html.ix-reel-held .ix-gut.is-b::after{height:100%;transform:none;}
  /* The closing clip leaving. #outro's own view timeline drives it on the compositor, so the
     frame is where the picture is in every frame the reader sees, however fast they scroll: over
     the exit (the story's foot from the screen's foot to its top) every box goes up by that much
     and what is in it comes down by the same. So nothing in the frame moves: the case studies
     come up over it, and the columns, the rail, the wave and the controls stay whole and in
     place above that rising edge until it reaches them, and never move towards the bar. The rail
     and the controls are boxes to the screen's foot, padded where they used to stop, and the
     wave's box starts to go up only once the rising edge is 16px under the wave (--ix-w0).
     Where a browser has no scroll timelines /js/rail.js writes --ix-lift instead. */
  .ix-rail,.ix-ctl{overflow-x:visible;overflow-y:clip;}
  .ix-prog{overflow:clip;overflow-clip-margin:16px;}
  @supports (animation-timeline:view()){
    .ix{timeline-scope:--ix-out;}
    .ix-story.ix-outro{view-timeline:--ix-out block;view-timeline-inset:0px;}
    .ix-gut,.ix-rail,.ix-ctl{animation:ix-frame-lift linear both;animation-timeline:--ix-out;animation-range:exit 0% exit 100%;}
    .ix-gut.is-l::before,.ix-gut.is-l::after,.ix-gut.is-r::before,.ix-gut.is-r::after,
    .ix-gut.is-t::before,.ix-gut.is-t::after,.ix-rail-list,.ix-ctl>*{animation:ix-frame-stay linear both;animation-timeline:--ix-out;animation-range:exit 0% exit 100%;}
    .ix-prog{animation:ix-wave-lift linear both;animation-timeline:--ix-out;animation-range:exit var(--ix-w0) exit 100%;}
    .ix-prog>*{animation:ix-wave-stay linear both;animation-timeline:--ix-out;animation-range:exit var(--ix-w0) exit 100%;}
  }
  @supports not (animation-timeline:view()){
    .ix-rail,.ix-ctl{translate:0 calc(-1 * var(--ix-lift,0px));}
    .ix-rail-list,.ix-ctl>*{translate:0 var(--ix-lift,0px);}
    .ix-prog{translate:0 calc(-1 * max(0px,var(--ix-lift,0px) - var(--ix-w0)));}
    .ix-prog>*{translate:0 max(0px,var(--ix-lift,0px) - var(--ix-w0));}
  }
  /* the bar while the film is up: no 1px border, so it ends at 92px, a whole number of device
     pixels at every common scaling (at 93px, 125% and 150% put its edge and its blurred backdrop
     across a device pixel and drew a light row over the columns). Fixed, it moves nothing below
     it. The border and the cyan inset are layered into the hairline, which spans the picture
     only, and not even that where the bar's edge lies on a strip. */
  html.ix-js:not(.ix-static):has(.ix-rail.is-film) .cx-header{border-bottom:0;
    box-shadow:inset 0 1px 0 rgba(221,214,255,.12);}
  html.ix-js:not(.ix-static):has(.ix-rail.is-film) .cx-header::before{box-shadow:none;}
  html.ix-js:not(.ix-static):has(.ix-rail.is-film) .cx-header::after{height:2px;left:var(--ix-gut);right:var(--ix-gut);
    opacity:clamp(0,calc((var(--ix-hb) - var(--ix-ptop)) / 1px),1);
    background:
      linear-gradient(90deg,transparent,rgba(168,85,247,.5),rgba(34,211,238,.4),transparent) top/100% 1px no-repeat,
      linear-gradient(rgba(34,211,238,.10),rgba(34,211,238,.10)) top/100% 1px no-repeat,
      linear-gradient(var(--glass-border),var(--glass-border)) bottom/100% 1px no-repeat;}

  /* the rail fills the left column between the bar and the foot less --ix-clr at each end,
     centred there; its size is the smaller of what the column is wide enough for and what that
     height can stand. The box runs on to the screen's foot, padded, for the closing exit above. */
  .ix-rail{left:0;width:var(--ix-gut);top:var(--ix-hb);bottom:0;padding:var(--ix-clr) 0;transform:none;
    display:flex;justify-content:center;align-items:center;
    --f:clamp(10px,min(calc(var(--ix-gut) * .208),calc((100svh - var(--ix-hb) - 2 * var(--ix-clr)) / 60)),17.6px);}
  .ix-rail.is-film{opacity:1;visibility:visible;pointer-events:auto;transition:opacity .45s ease;}
  /* measured: the rail is 59 times its type size tall with the four names, 36 with the short
     forms. A window that cannot stand the names at the 10px floor with room to spare first
     closes up the hairlines between them, then takes the short forms: never smaller type. */
  @media (max-height:800px){
    .ix-rail{--f:clamp(10px,min(calc(var(--ix-gut) * .208),calc((100svh - var(--ix-hb) - 2 * var(--ix-clr)) / 58)),17.6px);}
    .ix-rail-list{gap:calc(var(--f) * .85);}
    .ix-rail-i i{height:calc(var(--f) * 1.2);} .ix-rail-i.is-on i{height:calc(var(--f) * 2.1);}
  }
  @media (max-height:705px){
    .ix-rail{--f:clamp(10px,min(calc(var(--ix-gut) * .208),calc((100svh - var(--ix-hb) - 2 * var(--ix-clr)) / 38)),17.6px);}
    /* the full name leaves the screen but stays the link's name; the short form is generated
       content with empty alternative text, so it is neither read out nor in the link's text */
    .ix-rail-i .ix-rail-long{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;}
    .ix-rail-i::after{content:attr(data-short) / "";}
    .ix-rail-i.is-on::after{animation:ix-rail-beat 2.6s ease-in-out infinite;}
  }

  /* the progress wave, in the right column, belongs to the frame: up with it for the whole film
     and gone with it. Its number is centred on the first dash, so the wave starts half a line
     lower than the rail and the number clears the bar by the same --ix-clr. */
  .ix-prog{width:var(--ix-gut);top:calc(var(--ix-hb) + var(--ix-clr) + 8px);opacity:0;
    /* and on a short window it stops above the controls at the foot of the same column, with
       at least 24px between its last dash and the skip chip */
    height:var(--ix-progh);}
  .ix-rail.is-film ~ .ix-prog{opacity:1;}
  .ix-prog-dashes{padding-right:clamp(6px,calc(var(--ix-gut) * .12),18px);}
  .ix-prog-dashes i{width:clamp(10px,calc(var(--ix-gut) * .28),26px);}
  /* the number no smaller than it was beside the picture before the frame */
  .ix-prog-pct{right:calc(clamp(6px,calc(var(--ix-gut) * .12),18px) + clamp(10px,calc(var(--ix-gut) * .28),26px) + 7px);
    font-size:max(clamp(9.5px,calc(var(--ix-gut) * .14),13px),clamp(9.5px,calc(var(--ix-lgut) * .14),13px));}

  /* the film's pair of controls, centred in the right column at its foot, above the frame. On a
     desktop the skip is offered whenever a clip runs, the opening one included, which plays on
     load without holding the page, and the cue gives way to it (/js/reel.js, syncCtl); the cue
     stands whenever no clip runs. The skip keeps its slot while it is hidden, so the cue under it
     never moves, and the two cross fade as a clip starts, so they stand at least 16px apart.
     Everything in the pair is sized from --ix-ctlv, never smaller than the pair was beside the
     picture before the frame, and centred in the column's width. The box runs to the screen's
     foot, padded, for the closing exit above, with room over the skip for its ring. */
  .ix-reel-ui{display:none;}
  .ix-ctl{display:flex;position:fixed;z-index:6;--w:var(--ix-ctlv);
    right:0;bottom:0;width:var(--ix-gut);padding:8px 0 var(--ix-clr);
    flex-direction:column-reverse;align-items:center;gap:max(16px,calc(var(--w) * .2));
    opacity:0;visibility:hidden;pointer-events:none;transition:opacity .45s ease,visibility 0s linear .45s;}
  .ix-rail.is-film ~ .ix-ctl{opacity:1;visibility:visible;transition:opacity .45s ease;}
  .ix-ctl>*{pointer-events:auto;}
  .ix-ctl .ix-reel-skip{opacity:1;}
  .ix-ctl .ix-reel-skip[hidden]{display:flex;visibility:hidden;opacity:0;}
  .ix-ctl .ix-reel-skip:not([hidden])::before{animation:ix-skip-ring 2.4s cubic-bezier(.2,.7,.2,1) infinite;}
  .ix-ctl .ix-reel-skip:not([hidden]) .ix-skip-i{animation:ix-skip-nudge 2.4s ease-in-out infinite;}
  .ix-ctl .ix-cue{opacity:0;pointer-events:none;}
  html:not(.ix-reel-held):not(.ix-reel-live) .ix-ctl .ix-cue{opacity:1;}
  html:not(.ix-reel-held):not(.ix-reel-live) .ix-rail.is-film ~ .ix-ctl .ix-cue i::before{
    animation:ix-cue-wheel 2s cubic-bezier(.45,0,.35,1) infinite;}

  /* the copy walks on the picture: its column starts at the picture's edge, inset in picture
     units, and centres in the part of the picture below the bar, 16px clear of the bar and of
     the picture's foot */
  html.ix-js:not(.ix-static) .ix-story.has-reel .ix-col{left:var(--ix-gut);
    padding-left:clamp(24px,calc(var(--ix-pw) * .06),96px);
    top:calc(max(var(--header-h,92px),var(--ix-ptop)) + 16px);bottom:calc(var(--ix-pbot) + 16px);}
  html.ix-js:not(.ix-static) .ix-story.has-reel .ix-col.is-right{left:auto;right:var(--ix-gut);
    padding-left:0;padding-right:clamp(24px,calc(var(--ix-pw) * .06),96px);}
  html.ix-js:not(.ix-static) .ix-story.is-hero.has-reel .ix-hero-copy{bottom:calc(var(--ix-pbot) + clamp(26px,5vh,58px));}
  /* A block taller than the copy column reflows rather than shrinking its type. The column is
     the picture's height less the bar and two 16px clearances, and on a window squarer than 16:9
     that is set by the width, not the height (464px at 1025 wide however tall the window is), so
     /js/rail.js measures the blocks against the column and gives all four chapters the first of
     these that fits every one of them: `is-fit-d`, the description's short form, the one the
     narrow layout already has; for the resolved block, `is-fit-1` its plates two and one with
     every line, `is-fit-2` the plates stacked with their heading and answer only, as on a phone,
     `is-fit-3` both. */
  .ix-chapter .ix-col.is-fit-d .ix-p.is-long{display:none;}
  .ix-chapter .ix-col.is-fit-d .ix-p.is-short{display:block;}
  .ix-chapter .ix-col:is(.is-fit-1,.is-fit-3) .ix-block.is-resolved .ix-plates{grid-template-columns:repeat(2,minmax(0,1fr));}
  .ix-chapter .ix-col:is(.is-fit-1,.is-fit-3) .ix-block.is-resolved .ix-plate:nth-child(3){grid-column:1/-1;}
  /* half as wide, a plate's name and its tag take a line each rather than break mid word */
  .ix-chapter .ix-col:is(.is-fit-1,.is-fit-3) .ix-block.is-resolved .ix-plate-h{flex-wrap:wrap;row-gap:2px;}
  .ix-chapter .ix-col:is(.is-fit-2,.is-fit-3,.is-fit-4) .ix-block.is-resolved .ix-plate p:not(.ix-res){display:none;}
  /* `is-fit-4`, for a window short for its width: the plates with their heading and answer only
     and the column as wide as the picture less its inset at each side, the three plates side by
     side in it, so the block takes fewer lines instead of smaller type */
  html.ix-js:not(.ix-static) .ix-chapter.has-reel .ix-col.is-fit-4{width:calc(var(--ix-pw) - clamp(24px,calc(var(--ix-pw) * .06),96px));}
  .ix-chapter .ix-col.is-fit-4 .ix-col-in{max-width:none;}
  .ix-chapter .ix-col.is-fit-4 .ix-block.is-resolved .ix-plates{grid-template-columns:repeat(3,minmax(0,1fr));}
  .ix-chapter .ix-col.is-fit-4 .ix-block.is-resolved .ix-plate-h{flex-wrap:wrap;row-gap:2px;}
  /* captions clear the bar by the same gap wherever the picture starts, and the film's display
     face gets room above its capitals, which rise past its tight line box */
  .ix-story.has-reel .ix-caps{--cap-top:calc(max(0px,var(--header-h,92px) - var(--ix-ptop)) + clamp(24px,3vh,44px));}
  .ix-caps .ix-type[data-at="tl"],.ix-caps .ix-type[data-at="tr"],.ix-caps .ix-type[data-at="t"]{padding-top:.14em;}
  /* A card or a state line slides in from the nearer edge, and that edge is a column now: from
     11.5vw out a card spent its first frames behind the column and came out of it cut off. It
     starts 24px inside the picture instead, the clearance every text layer keeps from a column,
     so it is whole and clear from its first frame; the curve, the tilt and the settle are the
     same. */
  .ix-caps .ix-card:is([data-at="tl"],[data-at="l"],[data-at="bl"]){--ex:calc(24px - var(--cap-x));}
  .ix-caps .ix-card:is([data-at="tr"],[data-at="r"],[data-at="br"]){--ex:calc(var(--cap-x) - 24px);}
  .ix-caps .ix-state-cap:is([data-at="tl"],[data-at="l"],[data-at="bl"]){--ex:max(-3vw,calc(24px - var(--cap-x)));}
  /* the closing line gives way on the first scroll within about 100px: the closing story pins for
     half a screen only, and the type's .5s fade let a fast reader carry it up under the bar */
  .ix-outro .ix-stage.is-reel-done .ix-cap.is-end{transition:opacity .12s ease,transform 1.2s cubic-bezier(.22,.61,.36,1),visibility 0s;}
}
html.ix-static .ix-ctl,html:not(.ix-js) .ix-ctl{display:none;}
@media (prefers-reduced-motion:reduce){ .ix-ctl .ix-reel-skip::before,.ix-ctl .ix-skip-i{animation:none!important;} }

/* ---------- light blue opening copy, air in the closing line, no violet titles (2026-09-23) ----------
   1 and 2 are desktop only, under the film frame's own query, so phones and tablets are as they
   were. 3 holds at every width.
   1. Shot 1. The owner found the line under the title unreadable in white over the sunlit floor,
      and the kicker above it not strong enough in its pale blue. Both take one stronger light
      blue, #75d4ff (hsl 199, 100%, 73%): a sky blue, clearly light, nowhere near the mid blue of
      a link. The line also takes the kicker's weight: Roboto at 700, the sans of the kicker's
      Roboto Mono and the face its 600 is actually drawn with (only 400, 500 and 700 load),
      where Gilroy Light was a hairline stroke. Same 15px, same measure, still three lines. Both
      keep their shadow stacks and gain a tight 1px dark edge in front of them, which is what
      holds a saturated ink where the floor is brightest. Measured pixel by pixel on the glyphs,
      each against the same pixel with the ink made transparent and its shadow left on, then
      the median, at 1280x720, 1440x900, 1920x1080 and 2560x1440 with the clip held at 1.2s and
      at its last frame: the line went from 2.6:1 to 2.7:1 (2.0:1 to 2.1:1 over the brightest
      quarter of the floor) to 8.5:1 to 9.0:1 (6.4:1 to 7.1:1), the kicker from 4.6:1 to 8.9:1
      (2.0:1 to 5.8:1) to 8.0:1 to 8.9:1 (5.6:1 to 7.4:1). Against the bare floor a saturated
      blue carries less light than white, so the gain is the weight and the edge, not the ink.
   2. The closing line, "Expertise / you can / rely on", is set in Anton, whose capitals fill
      the whole .86 line box, so the three lines touched with a pixel between them. Each line
      after the first now stands .24em lower; the first does not move, so the block keeps its
      top left anchor.
   3. The CI/CD chapter's accent is violet and its kickers and the names on its resolved plates
      were set in it, the last purple titles on the page. They take the light ramp the headings
      use. The hairlines keep the chapter's accent, as docs/filament-label.md has it. The label's
      line box is exactly its type size, so the ramp is given .2em above and below to paint into
      (a clipped fill has no ink outside its box) and the same margin back, which leaves its
      layout as it was. */
@media (min-width:1025px) and (hover:hover) and (pointer:fine) and (prefers-reduced-motion:no-preference){
  .ix-story.is-hero.has-reel .ix-label{color:#75d4ff;
    text-shadow:0 0 1px rgba(3,5,10,.9),0 1px 2px rgba(3,5,10,.98),0 2px 9px rgba(3,5,10,.94),0 0 20px rgba(3,5,10,.8);}
  /* The line itself is a light grey (owner, the same day, after a dark grey trial): a cool
     near white that holds its own on the sunlit floor through the full shadow stack and the
     tight dark edge, the treatment that took the light blue to 8.5:1 and more. */
  .ix-story.is-hero.has-reel .ix-sub{color:#dde1e7;font-family:var(--font-body);font-weight:700;
    text-shadow:0 0 1px rgba(3,5,10,.9),0 1px 2px rgba(3,5,10,.98),0 2px 8px rgba(3,5,10,.92),0 0 22px rgba(3,5,10,.78);}
  .ix-outro .ix-type .ln+.ln{margin-top:.24em;}
}
/* 3 stands outside the film's query: the owner asked for purple titles to go everywhere on the
   site, so the CI/CD kickers and plate names take the ramp on phones, tablets, narrow desktop
   windows and reduced motion as well. */
#cicd .ix-label,#cicd .ix-plate-h span:first-child{
  background:linear-gradient(135deg,#ffffff 0%,#e4f1ff 42%,#8fd8ff 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;}
#cicd .ix-label{padding:.2em 0;margin:-.2em 0;}
