/* Original Workframe product demonstrations, restored and themed by landing.css. */
.buildways{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:var(--sp-5); margin-top:var(--sp-7)}
.buildway{overflow:hidden; border:1px solid var(--s3); border-radius:var(--r-xl); background:var(--s0); box-shadow:var(--shadow-1)}
.buildway__copy{padding:var(--sp-6)}
.buildway__head{display:flex; align-items:center; justify-content:space-between; gap:var(--sp-3)}
.buildway__head h3{font-size:var(--fs-h3)}
.buildway__copy p{margin-top:var(--sp-3); color:var(--tx2); font-size:var(--fs-body); line-height:1.6}
.buildway__visual{min-height:270px; padding:var(--sp-4); border-top:1px solid var(--s3); background:var(--s1); display:grid; align-content:center}

/* ---------- Drag and drop (Build it visually) ----------
   An SVG rather than a screenshot: the point of the panel is the gesture, and
   a still frame cannot show a gesture. Fixed viewBox, so every coordinate the
   keyframes translate by is exact at any width.

   All three chips sit 42 units apart and all three target tracks sit 42 units
   apart, so a single drag path — translate(236, 38) — serves all three. The
   ghosts differ only by a 4s delay on the shared 12s clock. */
.dnd__svg{display:block; width:100%; height:auto}
.dnd-panel{fill:var(--s0); stroke:var(--s3)}
.dnd-title{font:600 11px/1 var(--sans); fill:var(--txm)}
.dnd-k{font:500 10px/1 var(--sans); fill:var(--txm)}
.dnd-chip rect,.dnd-ghost rect{fill:var(--s0); stroke:var(--s3)}
.dnd-chip text,.dnd-ghost text{font:500 11.5px/1 var(--sans); fill:var(--tx2)}
.dnd-ic{fill:color-mix(in srgb,var(--accent) 30%,transparent); stroke:none}
.dnd-track{fill:var(--s1); stroke:var(--s3)}
/* Base state is the finished timeline: no JS, a paused loop and reduced motion
   all leave a scene that is fully assembled rather than half-built. */
.dnd-clip{fill:color-mix(in srgb,var(--accent) 52%,transparent); stroke:none;
  transform-box:fill-box; transform-origin:left center}
.dnd-clip--alt{fill:color-mix(in srgb,var(--violet) 52%,transparent)}
.dnd-clip--wait{fill:color-mix(in srgb,var(--accent) 16%,transparent);
  stroke:color-mix(in srgb,var(--accent) 62%,transparent); stroke-dasharray:3 3}
/* The drop target, shown only while a piece is in the air above it. */
.dnd-slot{fill:none; stroke:var(--accent-deep); stroke-dasharray:4 4; opacity:0}
.dnd-ghost{opacity:0; transform-box:fill-box; transform-origin:center;
  filter:drop-shadow(0 8px 16px rgba(16,38,45,.22))}
.dnd-ghost rect:first-of-type{stroke:var(--accent-deep)}
.dnd-cur{fill:var(--tx); stroke:var(--s0); stroke-width:1.2}

[data-loop] .dnd-ghost{animation:dnd-drag 12s var(--ease-both) infinite; animation-delay:calc(var(--i) * 4s)}
@keyframes dnd-drag{
  0%{opacity:0; transform:translate(0,0) scale(1)}
  2%{opacity:1; transform:translate(0,0) scale(1.05)}
  13%{opacity:1; transform:translate(236px,38px) scale(1.05)}
  16%{opacity:1; transform:translate(236px,38px) scale(1)}
  18%,100%{opacity:0; transform:translate(236px,38px) scale(1)}
}
[data-loop] .dnd-slot{animation:dnd-slot 12s var(--ease-both) infinite; animation-delay:calc(var(--i) * 4s)}
@keyframes dnd-slot{0%,3%{opacity:0} 6%,13%{opacity:1} 16%,100%{opacity:0}}
[data-loop] .dnd-clip:not(.dnd-clip--set){animation:dnd-drop 12s var(--ease-both) infinite; animation-delay:calc(var(--i) * 4s)}
@keyframes dnd-drop{
  0%,13%{opacity:0; transform:scaleX(.22)}
  16%{opacity:1; transform:scaleX(1.03)}
  19%,100%{opacity:1; transform:scaleX(1)}
}
/* The palette chip reads as picked up while its ghost is travelling. */
[data-loop] .dnd-chip--drag{animation:dnd-lift 12s var(--ease-both) infinite; animation-delay:calc(var(--i) * 4s)}
@keyframes dnd-lift{0%,1%{opacity:1} 4%,15%{opacity:.34} 19%,100%{opacity:1}}

/* ---------- Import with AI ----------
   Same 12s clock as the panel beside it, so the two never beat against each
   other.

   The rail is drawn per step rather than as one absolutely positioned line
   down the whole box: the three steps do not have equal content, so any
   single-element rail would need a row height CSS cannot know. A connector
   hung off each step is measured from that step's own padding and badge, so
   it lands on the badge centres whatever the copy does. */
.import-flow{display:grid; gap:var(--sp-3); max-width:420px; margin:auto}
.import-step{position:relative; display:grid; grid-template-columns:34px 1fr; align-items:start; gap:var(--sp-3); padding:var(--sp-3) var(--sp-4); border:1px solid var(--s3); border-radius:var(--r-md); background:var(--s0)}
.import-step b{width:34px; height:34px; display:grid; place-items:center; border-radius:var(--r-full); background:var(--accent-wash); color:var(--accent-deep); font-size:13px}
.import-step span{display:grid; gap:2px}
.import-step strong{font-size:var(--fs-sm)}
.import-step small{font-size:12.5px; color:var(--txm)}
/* left: 16px padding + half the 34px badge, minus half the 2px rail.
   top: that same padding + the badge. bottom: down through the grid gap. */
.import-step:not(:last-child)::before,
.import-step:not(:last-child)::after{
  content:""; position:absolute; left:32px; width:2px; border-radius:2px;
  top:calc(var(--sp-3) + 34px); bottom:calc(var(--sp-3) * -1);
}
.import-step::before{background:var(--s3)}
.import-step::after{background:var(--accent-deep); transform-origin:top center}
/* Two connectors, two explicit windows — rather than one delayed animation,
   which would leave the second rail still full into the next cycle. */
[data-loop] .import-step:nth-child(1)::after{animation:if-conn1 12s var(--ease-both) infinite}
[data-loop] .import-step:nth-child(2)::after{animation:if-conn2 12s var(--ease-both) infinite}
@keyframes if-conn1{0%,28%{transform:scaleY(0)} 36%,100%{transform:scaleY(1)}}
@keyframes if-conn2{0%,61%{transform:scaleY(0)} 69%,100%{transform:scaleY(1)}}
[data-loop] .import-step{animation:if-step 12s var(--ease-both) infinite; animation-delay:calc(var(--i) * 4s)}
@keyframes if-step{
  0%{opacity:.5; border-color:var(--s3); box-shadow:none}
  4%,30%{opacity:1; border-color:color-mix(in srgb,var(--accent) 55%,var(--s3)); box-shadow:0 6px 18px var(--accent-wash)}
  36%,100%{opacity:.5; border-color:var(--s3); box-shadow:none}
}
[data-loop] .import-step b{animation:if-badge 12s var(--ease-both) infinite; animation-delay:calc(var(--i) * 4s)}
@keyframes if-badge{
  0%{background:var(--accent-wash); color:var(--accent-deep)}
  4%,30%{background:var(--accent-deep); color:var(--accent-ink)}
  36%,100%{background:var(--accent-wash); color:var(--accent-deep)}
}
/* Three script lines typing themselves, three dots thinking, three tracks
   filling — one small tell per step, all on the same clock. */
.if-lines{display:grid; gap:3px; margin-top:7px; max-width:158px}
.if-lines i{height:3px; border-radius:2px; background:color-mix(in srgb,var(--accent) 45%,transparent); transform-origin:left center}
.if-lines i:nth-child(2){width:86%}
.if-lines i:nth-child(3){width:62%}
[data-loop] .if-lines i{animation:if-type 12s var(--ease-both) infinite; animation-delay:calc(var(--i) * .45s)}
@keyframes if-type{0%,4%{transform:scaleX(0)} 13%,100%{transform:scaleX(1)}}
.if-dots{display:flex; gap:5px; margin-top:8px}
.if-dots i{width:6px; height:6px; border-radius:50%; background:var(--violet)}
[data-loop] .if-dots i{animation:if-pulse 1.2s var(--ease-both) infinite; animation-delay:calc(var(--i) * .15s)}
@keyframes if-pulse{0%,100%{opacity:.32; transform:translateY(0)} 45%{opacity:1; transform:translateY(-3px)}}
.if-lanes{display:grid; gap:3px; margin-top:7px; max-width:164px}
.if-lanes i{position:relative; height:6px; border-radius:3px; background:var(--s2); overflow:hidden}
.if-lanes i::after{content:""; position:absolute; inset:0; border-radius:3px;
  background:color-mix(in srgb,var(--accent) 55%,transparent); transform-origin:left center}
.if-lanes i:nth-child(2)::after{background:color-mix(in srgb,var(--violet) 55%,transparent)}
[data-loop] .if-lanes i::after{animation:if-lane 12s var(--ease-both) infinite; animation-delay:calc(var(--i) * .2s)}
@keyframes if-lane{0%,68%{transform:scaleX(0)} 78%,100%{transform:scaleX(1)}}

@media (max-width:820px){
  .buildways{grid-template-columns:1fr}
  .buildway__visual{min-height:0}
  /* Stacked, the two panels are read one after the other rather than compared
     side by side, so the step-by-step dimming loses its point. Light them all
     and keep only the per-step tells moving. */
  [data-loop] .import-step,[data-loop] .import-step b{animation:none}
  .import-step{opacity:1}
  .import-step::after{transform:scaleY(1)}
}

/* ---------- Bring your own media (Assets) ----------
   The one claim on this page a screenshot cannot make: the cast and the rooms
   are not ours. A still frame of a finished scene looks identical whether the
   image came from the starter library or from the researcher's own shoot, so
   the exchange itself has to be the thing on screen.

   Sixteen seconds rather than the usual twelve, because there are three
   things to say and not two: files arrive, a room is placed, an actor walks
   into it. At twelve the upload was over before it read.
   Nothing else on the page loops at 16s, so it never sits next to a demo it
   can beat against.

   The base state is the last frame — both files placed, the library full,
   both tags naming the researcher's own sources — so reduced motion, a
   stopped loop and a failed image request all leave a stage that is already
   customised. */
.byo{position:relative; display:grid; grid-template-columns:.85fr 1.15fr; gap:var(--sp-6); align-items:stretch}
.byo__side{display:grid; grid-template-rows:1fr auto; gap:var(--sp-3)}

/* The researcher's own library, as a file list rather than a Finder window:
   a window would date the panel to one operating system. */
.byo__files{position:relative; display:flex; flex-direction:column; gap:8px; padding:12px;
  border:1px solid var(--s3); border-radius:var(--r-md); background:var(--s0)}
.byo__files-head{display:flex; align-items:center; gap:var(--sp-3); padding:2px 2px 8px; border-bottom:1px solid var(--s3)}
.byo__files-t{font:500 13px/1 var(--sans); color:var(--txm)}
.byo__fmt{margin-left:auto; font:400 11.5px/1 var(--mono); color:var(--txm)}
.byo__file{display:grid; grid-template-columns:34px 1fr; align-items:center; gap:10px;
  padding:7px 8px; border:1px solid transparent; border-radius:var(--r-sm)}
.byo__file strong{display:block; font:500 12.5px/1.3 var(--sans); color:var(--tx2)}
.byo__file small{display:block; font:400 11.5px/1.3 var(--sans); color:var(--txm)}
/* Thumbnails are drawn, not photographed. At 34px a real crop is mud, and
   these only have to read as "a picture of a room" and "a picture of a
   person". */
.byo__thumb{position:relative; height:26px; border-radius:5px; overflow:hidden; background:var(--s2)}
.byo__thumb--scene{background:linear-gradient(160deg,
  color-mix(in srgb,var(--accent) 34%,var(--s2)), color-mix(in srgb,var(--violet) 26%,var(--s2)))}
.byo__thumb--scene::after{content:""; position:absolute; left:4px; right:4px; bottom:4px; height:7px;
  border-radius:2px; background:color-mix(in srgb,var(--s0) 62%,transparent)}
.byo__thumb--actor::before{content:""; position:absolute; left:50%; top:4px; width:8px; height:8px;
  margin-left:-4px; border-radius:50%; background:var(--s4)}
.byo__thumb--actor::after{content:""; position:absolute; left:50%; bottom:0; width:16px; height:11px;
  margin-left:-8px; border-radius:8px 8px 0 0; background:var(--s4)}
.byo__thumb--alt{background:linear-gradient(160deg,var(--s3),var(--s2))}

/* ----- The upload itself -----
   Asked for and worth the room: "you can bring your own" is a claim about
   getting files in, and a panel that starts with the files already listed
   never shows that happening. The zone is the bottom of the library panel,
   where the empty space was. */
.byo__up{margin-top:auto; display:grid; grid-template-columns:32px 1fr; gap:10px; align-items:center;
  padding:13px 14px; border:1.5px dashed var(--s3); border-radius:var(--r-sm)}
.byo__up-ic{display:grid; place-items:center; width:32px; height:32px; border-radius:var(--r-sm);
  background:var(--accent-wash); color:var(--accent-deep)}
.byo__up-ic svg{display:block}
.byo__up-main{display:grid; gap:7px; min-width:0}
/* Three labels stacked in one cell, so the row never changes height as the
   wording changes. */
.byo__up-t{display:grid; font:500 12.5px/1.35 var(--sans)}
.byo__up-t b{grid-area:1 / 1; font-weight:500}
.byo__up-t--idle{color:var(--txm)}
.byo__up-t--busy{color:var(--tx2); opacity:0}
.byo__up-t--done{color:var(--accent-deep)}
.byo__up-bar{height:5px; border-radius:3px; background:var(--s2); overflow:hidden}
.byo__up-bar i{display:block; height:100%; border-radius:3px; background:var(--accent-deep); transform-origin:left center}
/* The files landing on the zone. Measured from the library panel rather than
   from .byo, so the drop still lands on the zone when the two columns stack. */
.byo__up-drop{position:absolute; z-index:2; left:50%; bottom:74px; transform:translateX(-50%);
  padding:6px 12px; border:1px solid var(--accent-deep); border-radius:var(--r-full);
  background:var(--s0); box-shadow:var(--shadow-1); white-space:nowrap;
  font:500 11.5px/1 var(--mono); color:var(--accent-deep); opacity:0}

/* ----- The file in flight -----
   `left` rather than a translate, for the same reason the embed chip uses it:
   only the container knows how far apart two columns are. */
.byo__fly{
  position:absolute; z-index:2; left:70%; transform:translate(-50%,-50%);
  display:flex; align-items:center; gap:8px; padding:6px 12px 6px 7px;
  border:1px solid var(--accent-deep); border-radius:var(--r-full);
  background:var(--s0); box-shadow:var(--shadow-1); white-space:nowrap;
  font:500 12px/1 var(--mono); color:var(--accent-deep); opacity:0;
}
.byo__fly i{width:22px; height:16px; border-radius:4px; background:color-mix(in srgb,var(--accent) 40%,var(--s2))}
.byo__fly--scene{top:29%}
.byo__fly--actor{top:39%}

/* ----- The stage -----
   Same 16:9 participant surface the hero uses, and the same dark chrome in
   both landing themes for the same reason: that is how it looks on the
   participant's screen whatever the researcher picked here. */
.byo__stage{
  position:relative; aspect-ratio:16/9; overflow:hidden;
  border:1px solid var(--s3); border-radius:var(--r-lg); background:#1b2226;
}
.byo__bg{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
/* What an unset scene looks like before anything is dropped on it. */
.byo__empty{position:absolute; inset:0; display:grid; place-items:center; opacity:0;
  background:
    repeating-linear-gradient(45deg, rgba(255,255,255,.035) 0 8px, transparent 8px 16px),
    #232b30;
  font:500 12.5px/1 var(--sans); color:rgba(255,255,255,.44)}
/* Drop outline, shown only while a file is over the stage. */
.byo__slot{position:absolute; inset:8px; border:1.5px dashed var(--accent); border-radius:var(--r-md); opacity:0}
/* The numbers are the builder's own staged-actor preset — x 34, y 66,
   scale 82 — which is where a character stands when nothing has moved it. The
   stage holds the dropped room on its own between the two drops: nobody is in
   it until the researcher's own file arrives. */
.byo__actor{position:absolute; left:34%; top:66%; height:78%; width:auto;
  transform:translate(-50%,-50%); filter:drop-shadow(0 12px 18px rgba(0,0,0,.34))}
/* Bottom right, on the empty floor: the top of the frame is where the actor's
   head is, and a label over a face is the one place it must not go. */
.byo__tags{position:absolute; right:4%; bottom:6%; display:flex; gap:6px}
.byo__tag{padding:5px 10px; border-radius:var(--r-full);
  font:500 11.5px/1 var(--sans); color:#eef3f4;
  border:1px solid rgba(255,255,255,.18); background:rgba(16,22,24,.72)}
.byo__tag b{font-weight:600; color:#6ed6c2}

/* ----- One clock, three acts -----
   0-24%   two files upload and join the library
   26-40%  the room is dragged onto the stage
   50-62%  the actor is dragged into the room
   62-100% the finished frame, held  */
[data-loop] .byo__up{animation:byo-zone 16s var(--ease-both) infinite}
@keyframes byo-zone{
  0%,3%{border-color:var(--s3); background:transparent}
  6%,20%{border-color:var(--accent-deep); background:var(--accent-wash)}
  25%,100%{border-color:var(--s3); background:transparent}
}
[data-loop] .byo__up-drop{animation:byo-drop 16s var(--ease-both) infinite}
@keyframes byo-drop{
  0%,1%{opacity:0; transform:translateX(-50%) translateY(-34px) scale(1.06)}
  4%{opacity:1; transform:translateX(-50%) translateY(-16px) scale(1.06)}
  7%{opacity:1; transform:translateX(-50%) translateY(0) scale(1)}
  9%,100%{opacity:0; transform:translateX(-50%) translateY(0) scale(1)}
}
[data-loop] .byo__up-bar i{animation:byo-bar 16s var(--ease-both) infinite}
@keyframes byo-bar{0%,7%{transform:scaleX(0)} 19%,100%{transform:scaleX(1)}}
/* Hard cuts, not crossfades: two lines of text sharing one cell read as a
   double exposure for as long as both are partly visible. */
[data-loop] .byo__up-t--idle{animation:byo-lbl-idle 16s step-end infinite}
@keyframes byo-lbl-idle{0%{opacity:1} 6%,100%{opacity:0}}
[data-loop] .byo__up-t--busy{animation:byo-lbl-busy 16s step-end infinite}
@keyframes byo-lbl-busy{0%{opacity:0} 6%{opacity:1} 20%,100%{opacity:0}}
[data-loop] .byo__up-t--done{animation:byo-lbl-done 16s step-end infinite}
@keyframes byo-lbl-done{0%{opacity:0} 20%,100%{opacity:1}}
/* The two uploaded rows do not exist until the upload finishes. */
[data-loop] .byo__file--scene{animation:byo-row-a 16s var(--ease-both) infinite}
@keyframes byo-row-a{
  0%,19%{opacity:0; transform:translateY(-6px); border-color:transparent; background:transparent}
  23%,25%{opacity:1; transform:none; border-color:transparent; background:transparent}
  29%,37%{opacity:1; transform:none; border-color:var(--accent-deep); background:var(--accent-wash)}
  41%,100%{opacity:1; transform:none;
    border-color:color-mix(in srgb,var(--accent) 42%,var(--s3)); background:var(--accent-wash)}
}
[data-loop] .byo__file--actor{animation:byo-row-b 16s var(--ease-both) infinite}
@keyframes byo-row-b{
  0%,21%{opacity:0; transform:translateY(-6px); border-color:transparent; background:transparent}
  25%,49%{opacity:1; transform:none; border-color:transparent; background:transparent}
  53%,60%{opacity:1; transform:none; border-color:var(--accent-deep); background:var(--accent-wash)}
  64%,100%{opacity:1; transform:none;
    border-color:color-mix(in srgb,var(--accent) 42%,var(--s3)); background:var(--accent-wash)}
}
[data-loop] .byo__fly--scene{animation:byo-fly-a 16s var(--ease-both) infinite}
@keyframes byo-fly-a{
  0%,27%{left:22%; opacity:0}
  30%{left:24%; opacity:1}
  37%{left:68%; opacity:1}
  39%,100%{left:70%; opacity:0}
}
[data-loop] .byo__fly--actor{animation:byo-fly-b 16s var(--ease-both) infinite}
@keyframes byo-fly-b{
  0%,51%{left:22%; opacity:0}
  54%{left:24%; opacity:1}
  60%{left:68%; opacity:1}
  62%,100%{left:70%; opacity:0}
}
/* Two windows on one element: the outline appears once per drop, and the
   second appearance has to be authored rather than delayed, or it would run
   past the end of the cycle. */
[data-loop] .byo__slot{animation:byo-slot 16s var(--ease-both) infinite}
@keyframes byo-slot{
  0%,29%{opacity:0} 32%,37%{opacity:1} 40%,53%{opacity:0}
  56%,60%{opacity:1} 63%,100%{opacity:0}
}
[data-loop] .byo__empty{animation:byo-empty 16s var(--ease-both) infinite}
@keyframes byo-empty{0%,35%{opacity:1} 40%,100%{opacity:0}}
[data-loop] .byo__bg{animation:byo-bg 16s var(--ease-both) infinite}
@keyframes byo-bg{0%,36%{opacity:0} 41%,100%{opacity:1}}
[data-loop] .byo__actor{animation:byo-actor 16s var(--ease-both) infinite}
@keyframes byo-actor{
  0%,60%{opacity:0; transform:translate(-50%,-50%) scale(.96)}
  65%,100%{opacity:1; transform:translate(-50%,-50%) scale(1)}
}
[data-loop] .byo__tag--scene{animation:byo-tag-a 16s var(--ease-both) infinite}
@keyframes byo-tag-a{0%,38%{opacity:0; transform:translateY(4px)} 43%,100%{opacity:1; transform:none}}
[data-loop] .byo__tag--actor{animation:byo-tag-b 16s var(--ease-both) infinite}
@keyframes byo-tag-b{0%,61%{opacity:0; transform:translateY(4px)} 66%,100%{opacity:1; transform:none}}

@media (max-width:900px){
  /* Once the panels stack, the hand-off changes axis with them. Keeping the
     chip in motion matters: it is the only moment that proves an uploaded
     source becomes something placeable, rather than two unrelated panels. */
  .byo{grid-template-columns:1fr; gap:var(--sp-5)}
  .byo__fly{display:flex; left:50%; top:0}
  [data-loop] .byo__fly--scene{animation-name:byo-fly-a-stack}
  [data-loop] .byo__fly--actor{animation-name:byo-fly-b-stack}
  @keyframes byo-fly-a-stack{
    0%,27%{top:31%; opacity:0}
    30%{top:33%; opacity:1}
    37%{top:61%; opacity:1}
    39%,100%{top:63%; opacity:0}
  }
  @keyframes byo-fly-b-stack{
    0%,51%{top:38%; opacity:0}
    54%{top:40%; opacity:1}
    60%{top:65%; opacity:1}
    62%,100%{top:67%; opacity:0}
  }
}


.demo-anim{
  max-width:900px; margin-top:var(--sp-6); padding:var(--sp-5);
  border:1px solid var(--s3); border-radius:var(--r-xl);
  background:var(--s0); box-shadow:var(--shadow-1);
}
/* Two demos are diagrams rather than a screen, and 900px squeezes both into
   thumbnails. They take the full column instead. */
.demo-anim--wide{max-width:none}
.demo-anim__head{display:flex; align-items:center; gap:var(--sp-4); margin-bottom:var(--sp-4)}
.demo-anim__label{font:500 var(--fs-sm)/1.4 var(--sans); color:var(--txm)}

/* The stage reproduces the real participant surface: the same starter
   background and character cutouts the app ships with, and the same dark
   dialogue chrome. It keeps that chrome in both landing themes, because on a
   participant's screen it looks this way regardless of what the researcher
   picked here. */
.da-screen{
  position:relative; aspect-ratio:16/9; overflow:hidden;
  border:1px solid var(--s3); border-radius:var(--r-lg); background:#1b2226;
  --mint:#6ed6c2;
}
.da-bg{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
/* The app's character model, reproduced rather than approximated. In the
   runtime a character sprite is drawn at 74% of the stage height, anchored at
   its centre, placed at (x%, y%) and multiplied by transform.scale/100 — so
   these rules use height:74% with a centring translate, and every number below
   is one the builder actually emits:
     staged (two actors)   x 34 / 66, y 66, scale 82
     foreground-left       x 25,      y 70, scale 148 (x1.05 speaker bonus)
     listener while spoken to                         opacity .6
   The foreground speaker genuinely overflows the bottom of the frame at that
   scale; .da-screen crops it exactly as the runtime canvas does. */
.da-actor{
  position:absolute; top:66%; height:74%; width:auto;
  transform:translate(-50%,-50%) scale(.82);
  filter:drop-shadow(0 12px 18px rgba(0,0,0,.34));
}
.da-actor--a{left:34%}
.da-actor--b{left:66%}
/* Dialogue and interaction cards are centre-anchored too, on the same track
   defaults the runtime uses: dialogue y 82 width 72, interactions y 78 width
   54, and a foreground-left line's caption at x 72, y 72, width 46. */
.da-card{
  position:absolute; left:50%; top:82%; width:72%; opacity:0;
  transform:translate(-50%,-50%);
  padding:11px 15px; border-radius:11px;
  background:rgba(22,24,26,.93); backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  border:1px solid rgba(255,255,255,.07);
  box-shadow:0 10px 26px rgba(0,0,0,.32);
}
.da-card b{display:block; font:700 12px/1.4 var(--sans); color:var(--mint); letter-spacing:.005em}
.da-card__line{font:400 15px/1.4 var(--sans); color:#f4f7f7; margin-top:2px}
.da-card__chev{position:absolute; right:14px; top:50%; transform:translateY(-50%); color:rgba(255,255,255,.5)}
.da-card--1,.da-card--2{left:72%; top:72%; width:46%}
.da-card--choice{left:50%; top:78%; width:54%}
.da-opt{
  display:block; margin-top:7px; padding:7px 11px; border-radius:7px;
  border:1px solid rgba(255,255,255,.16); background:rgba(255,255,255,.04);
  font:500 13px/1.35 var(--sans); color:#e7edee;
}
.da-signal{
  position:absolute; right:4%; top:7%; opacity:0;
  padding:6px 11px; border-radius:var(--r-full);
  font:400 12px/1 var(--mono); color:#0f2622;
  background:var(--mint); box-shadow:0 6px 18px rgba(0,0,0,.28);
}
/* Playback chrome, exactly the row that sits under the real stage. */
.da-controls{
  display:flex; align-items:center; justify-content:center; gap:var(--sp-4);
  margin-top:var(--sp-3); font-size:12px; color:var(--txm);
}
.da-controls__btn{
  display:inline-flex; align-items:center; gap:6px; padding:5px 12px;
  border:1px solid var(--s3); border-radius:var(--r-md); color:var(--tx2); font-weight:500;
}
.da-controls__t{font-family:var(--mono); letter-spacing:.04em}

.da-tl{position:relative; margin-top:var(--sp-4)}
.da-lane{display:grid; grid-template-columns:112px 1fr; gap:var(--sp-3); align-items:center; margin-top:6px}
.da-lane__k{font:500 11px/1.3 var(--sans); color:var(--txm); text-align:right}
.da-lane__t{position:relative; height:16px; border-radius:5px; background:var(--s1); border:1px solid var(--s3)}
.da-clip{position:absolute; top:2px; bottom:2px; border-radius:3px; background:color-mix(in srgb,var(--accent) 52%,transparent)}
.da-clip--alt{background:color-mix(in srgb,var(--violet) 52%,transparent)}
.da-clip--wait{
  background:color-mix(in srgb,var(--accent) 16%,transparent);
  border:1px dashed color-mix(in srgb,var(--accent) 62%,transparent);
}
/* Overlay repeats the lane grid so the playhead's percentages are measured
   against the track column, not the whole row. */
.da-tl__over{position:absolute; inset:0; display:grid; grid-template-columns:112px 1fr; gap:var(--sp-3); pointer-events:none}
.da-tl__over > div{grid-column:2; position:relative}
.da-play{position:absolute; top:0; bottom:0; left:32%; width:2px; background:var(--accent-deep)}
.da-play::before{
  content:""; position:absolute; top:-5px; left:-4px;
  width:10px; height:10px; border-radius:50%; background:var(--accent-deep);
}
.demo-anim__alt{margin-top:var(--sp-4); font-size:var(--fs-sm); color:var(--txm); max-width:72ch}

/* One clock, nine tracks. Percentages are the same clock the playhead reads,
   so the panel and the timeline can never drift apart. */
.demo-anim__stage .da-actor--a{animation:da-actorA 14s var(--ease-both) infinite both}
.demo-anim__stage .da-actor--b{animation:da-actorB 14s var(--ease-both) infinite both}
.demo-anim__stage .da-card--1{animation:da-line1 14s var(--ease-both) infinite both}
.demo-anim__stage .da-card--2{animation:da-line2 14s var(--ease-both) infinite both}
.demo-anim__stage .da-card--choice{animation:da-choice 14s var(--ease-both) infinite both}
.demo-anim__stage .da-opt--pick{animation:da-pick 14s var(--ease-both) infinite both}
.demo-anim__stage .da-signal{animation:da-signal 14s var(--ease-both) infinite both}
.demo-anim__stage .da-play{animation:da-play 14s var(--ease-both) infinite both}

/* Dialogue uses the builder's foreground-speaker preset: the speaker moves
   close to stage left while the line occupies a compact column on the right.
   The ordinary two-person staging returns for the participant choice. */
@keyframes da-actorA{
  0%,3%{opacity:0; left:34%; top:66%; transform:translate(-50%,-50%) scale(.82)}
  9%,11%{opacity:1; left:34%; top:66%; transform:translate(-50%,-50%) scale(.82)}
  15%,23%{opacity:1; left:25%; top:70%; transform:translate(-50%,-50%) scale(1.554)}
  27%,46%{opacity:1; left:34%; top:66%; transform:translate(-50%,-50%) scale(.82)}
  51%,69%{opacity:1; left:25%; top:70%; transform:translate(-50%,-50%) scale(1.554)}
  73%,100%{opacity:1; left:34%; top:66%; transform:translate(-50%,-50%) scale(.82)}
}
/* The listener never moves — the runtime only dims them, to .6. */
@keyframes da-actorB{
  0%,6%{opacity:0}
  12%,14%{opacity:1}
  16%,23%{opacity:.6}
  27%,49%{opacity:1}
  53%,69%{opacity:.6}
  73%,100%{opacity:1}
}
@keyframes da-line1{
  0%,11%{opacity:0; transform:translate(-50%,calc(-50% + 6px))}
  15%,23%{opacity:1; transform:translate(-50%,-50%)}
  27%,100%{opacity:0; transform:translate(-50%,calc(-50% - 4px))}
}
@keyframes da-choice{
  0%,25%{opacity:0; transform:translate(-50%,calc(-50% + 10px)) scale(.98)}
  30%,45%{opacity:1; transform:translate(-50%,-50%) scale(1)}
  49%,100%{opacity:0; transform:translate(-50%,calc(-50% - 4px)) scale(.99)}
}
@keyframes da-pick{
  0%,37%{background:rgba(255,255,255,.04); border-color:rgba(255,255,255,.16); color:#e7edee}
  41%,46%{background:rgba(110,214,194,.16); border-color:#6ed6c2; color:#6ed6c2}
  50%,100%{background:rgba(255,255,255,.04); border-color:rgba(255,255,255,.16); color:#e7edee}
}
@keyframes da-line2{
  0%,46%{opacity:0; transform:translate(-50%,calc(-50% + 6px))}
  51%,69%{opacity:1; transform:translate(-50%,-50%)}
  73%,100%{opacity:0; transform:translate(-50%,calc(-50% - 4px))}
}
@keyframes da-signal{
  0%,70%{opacity:0; transform:scale(.9)}
  75%,85%{opacity:1; transform:none}
  90%,100%{opacity:0; transform:scale(.98)}
}
/* The hold between 26% and 46% is the point of the whole animation: the
   timeline stops dead until the participant answers. */
@keyframes da-play{
  0%{left:0%} 26%{left:32%} 46%{left:32%} 72%{left:76%} 88%,100%{left:100%}
}


.embed{position:relative; display:grid; grid-template-columns:.85fr 1.15fr; gap:var(--sp-6); align-items:stretch}
.embed__side{display:grid; grid-template-rows:1fr auto; gap:var(--sp-3)}
.embed__cap{margin:0; font-size:13.5px; color:var(--txm)}

.ecode{position:relative; display:grid; grid-template-rows:auto 1fr; border:1px solid var(--s3); border-radius:var(--r-md); background:var(--s0); overflow:hidden}
.ecode__bar{position:relative; display:flex; align-items:center; gap:var(--sp-3); padding:8px 10px 8px 12px; border-bottom:1px solid var(--s3); background:var(--s1)}
.ecode__t{font:500 13px/1 var(--sans); color:var(--txm)}
.ecode__copy{
  position:relative; margin-left:auto; display:inline-grid; place-items:center;
  min-width:74px; height:30px; padding:0 12px;
  border:1px solid var(--s3); border-radius:var(--r-full); background:var(--s0);
  font:600 12.5px/1 var(--sans); color:var(--tx2);
}
.ecode__copy span{grid-area:1 / 1}
.ecode__copy-b{opacity:0; color:var(--accent-deep)}
/* Deliberately an outline rather than a copyable line of code. The exact
   snippet is generated per study and per survey platform, so printing one
   here would be a URL someone tries to use. The shape is the message: a tag,
   an address, and two values highlighted as the ones that come from you. */
/* Opening and closing tags are real text — without them the outline could be
   any list of bars. Everything between them is the attribute block, indented
   the way attributes are, and spread down whatever height the survey sets:
   `gap` is the floor, space-between spends the rest. The min-height is for
   the stacked layout, where there is no tall neighbour to match. */
.ecode__skel{display:grid; grid-template-rows:auto 1fr auto; gap:13px; padding:18px; min-height:230px}
.sk-tag{font:400 13px/1 var(--mono); color:var(--txm)}
.sk-body{display:grid; gap:13px; align-content:space-between; padding-left:26px}
.sk-line{display:flex; align-items:center; gap:9px; min-width:0}
.sk{flex:0 1 var(--w); height:10px; border-radius:5px; background:var(--s2)}
.sk--tag{background:var(--s3)}
.sk--on{background:color-mix(in srgb,var(--accent) 34%,transparent)}
/* Parked on the copy button, so a frozen loop leaves it out of the way. */
.ecode__cursor{position:absolute; right:14px; top:8px; opacity:0; pointer-events:none}
.ecode__cursor svg{display:block; fill:var(--tx); stroke:var(--s0); stroke-width:1.2}

[data-loop] .ecode__cursor{animation:em-cur 12s var(--ease-both) infinite}
@keyframes em-cur{
  0%{opacity:0; transform:translate(26px,30px)}
  4%{opacity:1; transform:translate(22px,26px)}
  10%,15%{opacity:1; transform:translate(0,0)}
  20%,100%{opacity:0; transform:translate(0,0)}
}
[data-loop] .ecode__copy{animation:em-btn 12s var(--ease-both) infinite}
@keyframes em-btn{
  0%,10%{border-color:var(--s3); background:var(--s0)}
  13%,30%{border-color:var(--accent-deep); background:var(--accent-wash)}
  35%,100%{border-color:var(--s3); background:var(--s0)}
}
[data-loop] .ecode__copy-a{animation:em-lbl 12s var(--ease-both) infinite}
@keyframes em-lbl{0%,10%{opacity:1} 13%,30%{opacity:0} 35%,100%{opacity:1}}
[data-loop] .ecode__copy-b{animation:em-lbl-on 12s var(--ease-both) infinite}
@keyframes em-lbl-on{0%,10%{opacity:0} 13%,30%{opacity:1} 35%,100%{opacity:0}}

/* The snippet in flight. `left` rather than a translate: the distance it has
   to cover is the width of the two columns, which only the container knows. */
.embed__fly{
  position:absolute; z-index:2; top:44%; left:72%; transform:translate(-50%,-50%);
  padding:7px 12px; border:1px solid var(--accent-deep); border-radius:var(--r-full);
  background:var(--s0); box-shadow:var(--shadow-1); white-space:nowrap;
  font:500 12px/1 var(--mono); color:var(--accent-deep); opacity:0;
}
[data-loop] .embed__fly{animation:em-fly 12s var(--ease-both) infinite}
@keyframes em-fly{
  0%,15%{left:24%; opacity:0}
  18%{left:26%; opacity:1}
  26%{left:70%; opacity:1}
  29%,100%{left:72%; opacity:0}
}

.esurvey{border:1px solid var(--s3); border-radius:var(--r-md); background:var(--s0); overflow:hidden}
.esurvey__bar{display:flex; align-items:center; gap:var(--sp-3); padding:10px 14px; border-bottom:1px solid var(--s3); background:var(--s1); font:500 13px/1 var(--sans); color:var(--txm)}
.esurvey__dots{display:flex; gap:4px}
.esurvey__dots i{width:7px; height:7px; border-radius:50%; background:var(--s3)}
.esurvey__q{margin:0; padding:14px 16px 12px; font:500 14.5px/1.45 var(--sans); color:var(--tx2)}
.eslot{margin:0 16px; border:1.5px solid var(--s3); border-radius:var(--r-sm); background:var(--s1); overflow:hidden}
.eslot__scene{position:relative; aspect-ratio:16 / 9}
.eslot__bg{position:absolute; inset:0; width:100%; height:100%; object-fit:cover}
/* Same framing as the hero preset, cropped to a sixteen-by-nine thumbnail. */
.eslot__actor{position:absolute; left:4%; bottom:-8%; width:auto; height:104%; object-fit:contain}
.eslot__line{
  position:absolute; left:32%; right:5%; top:10%; padding:9px 12px; border-radius:9px;
  border:1px solid var(--glass-stroke); background:rgba(16,22,24,.74);
  font:400 12.5px/1.4 var(--sans); color:#eef3f4;
}
.eslot__line b{display:block; margin-bottom:3px; font:600 11.5px/1 var(--sans); color:#6ed6c2}
.eslot__opts{position:absolute; left:32%; right:5%; bottom:7%; display:grid; gap:6px}
.eslot__opt{padding:7px 10px; border:1px solid rgba(255,255,255,.22); border-radius:7px; background:rgba(16,22,24,.6); font:500 11.5px/1.25 var(--sans); color:#eef3f4}
.eslot__opt--pick{border-color:#6ed6c2; background:rgba(15,48,43,.72); color:#6ed6c2}
.esurvey__foot{display:flex; align-items:center; gap:var(--sp-3); padding:14px 16px}
.esurvey__saved{font:500 12.5px/1 var(--sans); color:var(--accent-deep)}
.esurvey__saved::before{content:"\2713\00a0"}
.esurvey__next{margin-left:auto; padding:9px 18px; border-radius:var(--r-full); background:var(--accent-deep); color:var(--accent-ink); font:600 13px/1 var(--sans)}

/* The paste lands at 28%; everything after it is the scene doing its job. */
[data-loop] .eslot{animation:em-slot 12s var(--ease-both) infinite}
@keyframes em-slot{
  0%,25%{border-style:dashed; border-color:var(--s4)}
  28%,100%{border-style:solid; border-color:var(--s3)}
}
[data-loop] .eslot__scene{animation:em-scene 12s var(--ease-both) infinite}
@keyframes em-scene{0%,25%{opacity:0} 33%,100%{opacity:1}}
[data-loop] .eslot__line{animation:em-say 12s var(--ease-both) infinite}
@keyframes em-say{0%,36%{opacity:0; transform:translateY(4px)} 43%,100%{opacity:1; transform:none}}
[data-loop] .eslot__opts{animation:em-opts 12s var(--ease-both) infinite}
@keyframes em-opts{0%,50%{opacity:0; transform:translateY(4px)} 57%,100%{opacity:1; transform:none}}
[data-loop] .eslot__opt--pick{animation:em-pick 12s var(--ease-both) infinite}
@keyframes em-pick{
  0%,64%{border-color:rgba(255,255,255,.22); background:rgba(16,22,24,.6); color:#eef3f4}
  69%,100%{border-color:#6ed6c2; background:rgba(15,48,43,.72); color:#6ed6c2}
}
[data-loop] .esurvey__saved{animation:em-saved 12s var(--ease-both) infinite}
@keyframes em-saved{0%,72%{opacity:0} 78%,100%{opacity:1}}
[data-loop] .esurvey__next{animation:em-next 12s var(--ease-both) infinite}
@keyframes em-next{
  0%,74%{background:var(--s2); color:var(--txm)}
  80%,100%{background:var(--accent-deep); color:var(--accent-ink)}
}


.collect{display:grid; grid-template-columns:1.25fr .75fr; gap:var(--sp-7); align-items:center}
.collect__svg{display:block; width:100%; height:auto}
.cperson{fill:var(--s4)}
.cwire{fill:none; stroke:var(--s3); stroke-width:1.5; stroke-dasharray:4 4}
.cbox{fill:var(--s1); stroke:var(--s3); stroke-width:1.5}
.cbox--file{fill:var(--s0)}
.cfold{fill:var(--s2)}
.cslot{fill:color-mix(in srgb,var(--accent) 38%,transparent)}
.cled{fill:var(--accent-deep)}
.ct{font:500 11px/1 var(--sans); fill:var(--tx2)}
.ct--k{font:400 10px/1 var(--sans); fill:var(--txm)}
.ct--m{font:500 11px/1 var(--mono); fill:var(--accent-deep)}
/* Hidden unless the browser can actually put them on the path — without
   offset-path they would pile up in the corner of the viewBox. */
.cdot{fill:var(--accent-deep); opacity:0}

@supports (offset-path: path("M0 0 L1 1")) {
  [data-loop] .cdot{animation:cw-in 4.5s var(--ease-both) infinite; animation-delay:calc(var(--i) * .17s)}
  [data-loop] .cdot:nth-child(1){offset-path:path("M46 38 C 104 38, 112 125, 168 125")}
  [data-loop] .cdot:nth-child(2){offset-path:path("M46 92 C 104 92, 112 125, 168 125")}
  [data-loop] .cdot:nth-child(3){offset-path:path("M46 146 C 104 146, 112 125, 168 125")}
  [data-loop] .cdot:nth-child(4){offset-path:path("M46 200 C 104 200, 112 125, 168 125")}
  [data-loop] .cdot--out:nth-child(5){offset-path:path("M252 125 C 288 125, 292 79, 318 79")}
  [data-loop] .cdot--out:nth-child(6){offset-path:path("M252 125 C 288 125, 292 171, 318 171")}
  [data-loop] .cdot--out{animation:cw-out 4.5s var(--ease-both) infinite; animation-delay:calc(var(--i) * .16s)}
}
@keyframes cw-in{
  0%{offset-distance:0%; opacity:0}
  4%{opacity:1}
  26%{offset-distance:100%; opacity:1}
  29%,100%{offset-distance:100%; opacity:0}
}
@keyframes cw-out{
  0%,50%{offset-distance:0%; opacity:0}
  54%{opacity:1}
  64%{offset-distance:100%; opacity:1}
  67%,100%{offset-distance:100%; opacity:0}
}
/* The store takes each arrival, then hands both files over at once. */
[data-loop] .cled{animation:cw-led 4.5s var(--ease-both) infinite}
@keyframes cw-led{0%,10%{opacity:.3} 14%,44%{opacity:1} 50%,100%{opacity:.3}}
[data-loop] .cfile{animation:cw-file 4.5s var(--ease-both) infinite}
@keyframes cw-file{
  0%,60%{opacity:.35; transform:translateY(4px)}
  68%,100%{opacity:1; transform:none}
}
.cfile{transform-box:fill-box; transform-origin:center}

.collect__rows{display:grid; gap:6px; align-content:center; min-width:0}
.crow{
  display:grid; grid-template-columns:34px 1fr 1fr 36px 46px; gap:9px;
  padding:8px 11px; border:1px solid var(--s3); border-radius:var(--r-sm); background:var(--s0);
  font:400 11.5px/1.35 var(--mono); color:var(--tx2);
}
.crow i{font-style:normal; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.crow--h{padding:0 11px; border-color:transparent; background:none; color:var(--txm); font-size:9.5px; letter-spacing:.05em; text-transform:uppercase}
/* Four keyframe sets rather than four delays. A delay shifts the whole cycle,
   so the rows would also VANISH one at a time at the wrap — the export coming
   apart row by row. Staggering only the entrance leaves one shared exit. */
[data-loop] .crow--1{animation:cw-row1 4.5s var(--ease-both) infinite}
[data-loop] .crow--2{animation:cw-row2 4.5s var(--ease-both) infinite}
[data-loop] .crow--3{animation:cw-row3 4.5s var(--ease-both) infinite}
[data-loop] .crow--4{animation:cw-row4 4.5s var(--ease-both) infinite}
@keyframes cw-row1{0%,58%{opacity:0; transform:translateY(6px)} 65%,100%{opacity:1; transform:none}}
@keyframes cw-row2{0%,63%{opacity:0; transform:translateY(6px)} 70%,100%{opacity:1; transform:none}}
@keyframes cw-row3{0%,68%{opacity:0; transform:translateY(6px)} 75%,100%{opacity:1; transform:none}}
@keyframes cw-row4{0%,73%{opacity:0; transform:translateY(6px)} 80%,100%{opacity:1; transform:none}}

.dl-foot{display:flex; flex-wrap:wrap; align-items:center; gap:var(--sp-3); margin-top:var(--sp-5)}
.dl-foot__note{font-size:var(--fs-sm); color:var(--txm)}
.notes{display:grid; gap:var(--sp-3); max-width:74ch}
.notes li{font-size:var(--fs-sm); color:var(--tx2)}
.notes strong{color:var(--tx)}


@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; scroll-behavior:auto !important;
  }
  /* Every reveal renders at its final, fully informative state. */
  :root.io .reveal{opacity:1 !important; transform:none !important}
  .btn:hover{transform:none !important}
  /* The schematic falls back to the frame its base rules already describe:
     the scene, both actors and the open choice, playhead stopped on the
     interaction. Collapsing the animations instead would strand elements on
     whichever keyframe happened to be last. */
  .demo-anim__stage *{animation:none !important}
  /* With the animations off, the cards would all sit at their hidden base
     state and leave an empty scene. Show the moment the playhead is parked
     on — the interaction — so the still says the same thing the run does. */
  .demo-anim__stage .da-card--choice{opacity:1}
  .demo-anim__stage .da-opt--pick{background:rgba(110,214,194,.16); border-color:#6ed6c2; color:#6ed6c2}

  /* Same contract for every other looping demo: stop the loop, then pin the
     frame that carries the information. */
  [data-loop] *{animation:none !important}
  /* The embed demo already ends where its base state sits — snippet copied,
     scene running, Next unlocked. Only the pointer and the chip in flight
     cannot survive being frozen, so they go. */
  .ecode__cursor,.embed__fly{display:none}
  /* Same for the export diagram: the wires say what the dots were saying. */
  .cdot{display:none}
  /* The pieces are already on the timeline; only the hand carrying them there
     cannot survive being frozen, so the ghosts and drop targets go. */
  .dnd-ghost,.dnd-slot{display:none}
  .dnd-clip{opacity:1; transform:none}
  /* All three import steps lit, the rail drawn end to end, and every small
     tell at its finished width. */
  .import-step{opacity:1}
  .import-step::after{transform:scaleY(1)}
  .if-lines i,.if-lanes i::after{transform:none}
  .if-dots i{opacity:1; transform:none}
  /* Both files placed and both tags lit — the frame the loop settles on.
     Only the empty stage, the starter figure and the two chips in flight
     cannot survive being frozen, so they go. */
  .byo__fly,.byo__empty,.byo__slot,.byo__up-drop{display:none}
  .byo__bg,.byo__tag{opacity:1; transform:none}
  .byo__actor{opacity:1; transform:translate(-50%,-50%)}
  .byo__up-bar i{transform:none}
  .byo__up-t--idle,.byo__up-t--busy{opacity:0}
  .byo__file--scene,.byo__file--actor{opacity:1; transform:none;
    border-color:color-mix(in srgb,var(--accent) 42%,var(--s3)); background:var(--accent-wash)}
  /* This one already has its finished state as the base state. */
  .steps__rail{transform:none}
}
/* Adapter: use the revamp's type, spacing, and glass without changing the
   original demonstrations' geometry or shared timing. */
.product-motion{
  --s0:var(--glass-strong);--s1:color-mix(in srgb,var(--panel) 72%,transparent);
  --s2:color-mix(in srgb,var(--line) 48%,transparent);--s3:var(--line);
  --s4:var(--muted);--tx:var(--ink);--tx2:var(--ink);--txm:var(--muted);
  --accent-deep:var(--accent);--accent-strong:var(--accent);
  --accent-wash:color-mix(in srgb,var(--accent) 10%,transparent);
  --violet:color-mix(in srgb,var(--accent) 68%,var(--muted));
  --sans:Geist,Arial,sans-serif;--mono:'Geist Mono',monospace;
  --fs-sm:13px;--fs-body:15px;--fs-h3:22px;
  --sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:16px;--sp-5:24px;--sp-6:32px;--sp-7:48px;
  --r-sm:6px;--r-md:9px;--r-lg:12px;--r-xl:16px;--r-full:999px;
  --shadow-1:var(--glass-shadow);--ease-both:cubic-bezier(.45,0,.2,1);--settle:cubic-bezier(.16,1,.3,1);
}
.demo-anim.product-motion{max-width:none;margin:0;padding:28px;border-color:var(--glass-stroke);background:var(--glass);box-shadow:var(--glass-edge),var(--glass-shadow);backdrop-filter:blur(22px) saturate(1.25);-webkit-backdrop-filter:blur(22px) saturate(1.25)}
.buildways{margin:0 0 64px;gap:26px}
.buildway{border-color:var(--glass-stroke);background:var(--glass);box-shadow:var(--glass-edge),var(--glass-shadow);backdrop-filter:blur(22px) saturate(1.2);-webkit-backdrop-filter:blur(22px) saturate(1.2)}
.buildway__visual{background:color-mix(in srgb,var(--panel) 46%,transparent);border-top-color:var(--glass-stroke);padding:22px}
.buildway__copy{padding:28px}.buildway__head{align-items:flex-start}.buildway__head h3{letter-spacing:-.035em}
.buildway__copy p{font-size:14px}.buildway .badge{white-space:nowrap;font-size:10px;border:1px solid var(--line);border-radius:5px;padding:4px 7px;color:var(--muted)}
.product-motion .demo-anim__label{font-size:13px;color:var(--muted)}
.product-motion .chip{font-size:11px;background:var(--glass-strong);border:1px solid var(--glass-stroke);border-radius:6px;padding:6px 10px}
.product-motion .dl-foot{padding-top:20px;border-top:1px solid var(--glass-stroke)}
.product-motion .dl-foot__note{font-size:12px}
.product-motion .ecode,.product-motion .esurvey,.product-motion .byo__files{background:var(--glass);box-shadow:var(--glass-edge);border-color:var(--glass-stroke)}
.product-motion .import-step{box-shadow:var(--glass-edge)}
.workbench-tabs{margin-top:40px}
.results{display:block}.results-intro{margin-bottom:40px}.results-intro>p{max-width:600px}
.results .result-principles{margin-top:45px}.results .collect{grid-template-columns:1.2fr 1fr}
.story-layout{grid-template-columns:1.4fr 1fr;gap:7%}
.story-stage{top:100px}.story-stage .demo-anim{padding:20px}
.story-stage .da-card__line{font-size:14px}.story-stage .da-opt{font-size:12px}
.story-stage .da-lane,.story-stage .da-tl__over{grid-template-columns:85px 1fr;gap:10px}
.story-stage .demo-anim__alt{font-size:12px}.story-stage .demo-anim__head{margin-bottom:16px}
[data-loop]:not([data-motion-active]) *,[data-loop]:not([data-motion-active]) *::before,[data-loop]:not([data-motion-active]) *::after{animation-play-state:paused!important}
html:not(.motion-ready) [data-loop] *,html:not(.motion-ready) [data-loop] *::before,html:not(.motion-ready) [data-loop] *::after{animation:none!important}
html:not(.motion-ready) .da-card--choice{opacity:1}
html:not(.motion-ready) .byo__up-t--idle{opacity:0}
@media(max-width:1050px){.story-layout{display:block}.story-stage{position:relative;top:auto;max-width:820px}.story-copy{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;padding:35px 0 70px}.story-beat,.story-beat:first-child{display:block;min-height:0;padding:0}.story-beat h3{font-size:25px}.story-beat h3 br{display:none}.story-beat p{font-size:14px;margin-top:16px}.story-beat .story-detail{font-size:12px}}
@media(max-width:900px){.product-motion .embed{grid-template-columns:1fr;gap:24px}.product-motion .embed__fly{left:50%;top:0}[data-loop] .embed__fly{animation-name:em-fly-stack}@keyframes em-fly-stack{0%,15%{top:31%;opacity:0}18%{top:33%;opacity:1}26%{top:62%;opacity:1}29%,100%{top:64%;opacity:0}}.results .collect{grid-template-columns:1fr;gap:24px}}
@media(max-width:640px){.demo-anim.product-motion{padding:16px}.buildway__copy{padding:22px}.buildway__visual{padding:12px}.buildways{gap:22px;margin-bottom:40px}.story-copy{grid-template-columns:1fr;gap:28px}.story-stage .da-screen{aspect-ratio:4/5}.story-stage .da-card--1,.story-stage .da-card--2{left:50%;top:76%;width:calc(100% - 24px)}.story-stage .da-card--choice{left:50%;top:74%;width:calc(100% - 24px)}.story-stage .da-card__line{font-size:13px}.story-stage .da-opt{font-size:12px}.story-stage .da-signal{font-size:10px}.product-motion .collect__rows{gap:5px}.product-motion .crow{gap:6px;padding:8px;font-size:10px;grid-template-columns:28px 1fr 1fr 32px 43px}.product-motion .crow--h{font-size:9px}.product-motion .byo__tags{flex-wrap:wrap;justify-content:flex-end}.product-motion .byo__tag{font-size:10px;padding:4px 7px}.product-motion .dl-foot__note{flex-basis:100%}.product-motion .eslot__opts{left:8%;right:8%}.product-motion .eslot__line{font-size:11px}}
@media(prefers-reduced-transparency:reduce){.demo-anim.product-motion,.buildway,.product-motion .byo__files,.product-motion .ecode,.product-motion .esurvey{background:var(--elevated);backdrop-filter:none;-webkit-backdrop-filter:none}}
