/* ═════════════════════════════════════════════════════════════════════════
   The Knitting Studio — full bleed, near-black, one dock.
   ─────────────────────────────────────────────────────────────────────────
   What this replaces: a white card, inside a band, inside a wrap, with a
   headline stacked above the model and forty-five controls stacked below it.
   The garment had about a third of the fold and every control shouted at the
   same volume.

   Now the studio IS the screen. The cloth gets all of it; the controls are one
   translucent dock along the bottom showing ONE decision at a time — shape,
   then look, then your mark — and everything a specifier wants lives behind
   two words, Fine-tune and Spec. The stage's render box stops above the dock
   (`--dock-h`, set from JS) so the garment is never behind the glass.

   It was near-black for a while, and that read cold: a workshop at night with a
   readout in the corner. It is DAYLIGHT now — the garment lit from above in a
   bright room, frosted white controls, and the numbers turned down from a
   machine panel to one friendly line. A shop window, not a console.

   Every control here is a Bingo! component in its website register: chips are
   the system's Chip (8px, blue tint + blue border when selected), buttons are
   its pill Button, panel labels are overlines, selection is blue and never red,
   and the press is a 0.975 scale. What the studio adds is glass — the dock and
   the floating controls sit over the cloth, so they carry a blur and a
   translucent surface the app has no need for.
   ═════════════════════════════════════════════════════════════════════════ */
/* The section HOLDS the hero's first screen before studio.js has built
   anything inside it. It used to be an empty, zero-height box, so the first
   paint put the factory band — and its footage — at the top of the window, and
   the page shoved it down again the moment the studio appeared. Same height and
   same light as `.studio` here means the first paint is already the studio's
   bright room, waiting, and the factory video stays below the fold where its
   observer leaves it alone. */
.studio-sec,.studio{
  /* A bright room: light pouring in from above, a soft floor under the cloth. */
  background:
    radial-gradient(120% 46% at 50% 108%,rgba(29,27,22,.07) 0%,rgba(29,27,22,0) 60%),
    radial-gradient(112% 84% at 50% -16%,var(--stage-1) 0%,#FAF8F4 42%,var(--stage-2) 72%,
      var(--stage-3) 100%)}
.studio-sec{position:relative;padding:0;min-height:100svh}
/* What stands in that reserved screen until studio.js replaces #studio-root. */
.studio-boot{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:.8rem;font-size:var(--fs-body-sm);color:var(--text-secondary);
  font-weight:var(--fw-medium)}
.studio{position:relative;color:var(--ink);-webkit-user-select:none;user-select:none;
  /* the whole first screen — the header is fixed and hidden until you scroll */
  height:100svh;min-height:34rem;
  --dock-h:9rem;overflow:hidden}
.studio.dropping{box-shadow:inset 0 0 0 2px var(--blue-primary)}

/* ── The views ─────────────────────────────────────────────────────────── */
.views{position:absolute;inset:0}
.deck,.flat{position:absolute;inset:0}
.deck-track{display:flex;height:100%;overflow-x:auto;overflow-y:hidden;scroll-snap-type:x mandatory;
  scrollbar-width:none;-ms-overflow-style:none;overscroll-behavior-x:contain;touch-action:pan-x pan-y}
.deck-track::-webkit-scrollbar{display:none}
.deck-track.dragging{scroll-snap-type:none;cursor:grabbing}
.slide{flex:0 0 100%;height:100%;scroll-snap-align:center;position:relative;
  transition:opacity var(--dur-slow) var(--ease-out)}
.slide:not(.is-active){opacity:.45}
.stage{position:absolute;inset:0}
.stage-3d{position:absolute;left:0;right:0;top:0;bottom:var(--dock-h)}
.stage-skel{position:absolute;left:0;right:0;top:0;bottom:var(--dock-h);display:flex;
  flex-direction:column;align-items:center;justify-content:center;gap:.8rem;
  font-size:var(--fs-body-sm);color:var(--text-secondary);font-weight:var(--fw-medium);
  transition:opacity var(--dur-slow) var(--ease-out)}
.slide.ready .stage-skel{opacity:0;pointer-events:none}

/* ── Overlays on the cloth ─────────────────────────────────────────────── */
.ov{position:absolute;z-index:4;pointer-events:none}
.ov>*{pointer-events:auto}
.ov-tl{left:clamp(1.2rem,4vw,2.6rem);top:clamp(1.2rem,3vw,2rem);max-width:min(30rem,52%)}
.ov-tl .eyebrow{margin-bottom:.7rem}
.ov-tl h1{font-size:clamp(26px,3vw,var(--fs-display));line-height:var(--lh-tight);
  margin-bottom:.9rem;max-width:20ch}
.ov-tl p{color:var(--mid);font-size:1rem;line-height:1.55;max-width:28em}
.ov-tr{right:clamp(1.2rem,4vw,2.6rem);top:clamp(1.2rem,3vw,2rem);display:flex;gap:.4rem;
  align-items:flex-start;flex-wrap:wrap;justify-content:flex-end}
.ov-bl{left:clamp(1.2rem,4vw,2.6rem);bottom:calc(var(--dock-h) + .9rem)}

/* Machine readout: tabular, monospaced feel, sitting on the dark like a panel. */
/* One friendly line, not a machine panel. */
.readout{display:inline-flex;align-items:center;gap:var(--space-2);
  background:rgba(255,255,255,.8);border:1px solid var(--border);
  border-radius:var(--radius-pill);padding:.5rem .95rem;font-size:13px;
  font-weight:var(--fw-medium);color:var(--text-secondary);
  font-variant-numeric:tabular-nums lining-nums;font-feature-settings:var(--fnum);
  backdrop-filter:blur(14px) saturate(1.4);box-shadow:var(--shadow-pill)}
.readout .dot{width:7px;height:7px;border-radius:50%;background:var(--blue-primary);flex:none;
  animation:pulse 1.15s var(--ease-in-out) infinite}
.readout.is-done .dot{background:#2E9E63;animation:none}
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.7)}}

/* Glass controls: the view toggle, the turntable, undo. */
/* Frosted white controls, floating over the room. */
.gbtn{display:inline-flex;align-items:center;gap:var(--space-2);height:2.4rem;padding:0 .95rem;
  background:rgba(255,255,255,.76);border:1px solid var(--border);color:var(--text-secondary);
  font-size:13px;font-weight:var(--fw-semibold);backdrop-filter:blur(14px) saturate(1.4);
  border-radius:var(--radius-pill);box-shadow:var(--shadow-pill)}
.gbtn:hover:not(:disabled){color:var(--ink);background:#fff}
.gbtn .ic,.dock-x .ic,.drop .ic,.mkbtn .ic{flex:none}
.drop{align-items:center}
.gbtn:disabled{opacity:.4;cursor:not-allowed}
/* A segmented track: --radius-track, with the selected segment carrying the
   system's selection language rather than an ink fill. */
.gseg{display:inline-flex;background:rgba(255,255,255,.76);border:1px solid var(--border);
  backdrop-filter:blur(14px) saturate(1.4);border-radius:var(--radius-track);padding:3px;
  box-shadow:var(--shadow-pill)}
.gseg button{height:1.95rem;padding:0 .85rem;color:var(--text-secondary);font-size:13px;
  font-weight:var(--fw-medium);border-radius:7px}
.gseg button:hover{color:var(--ink)}
.gseg button.on{background:var(--blue-tint);color:var(--blue-primary);
  font-weight:var(--fw-semibold)}

/* CircleButton, over glass. */
.edge{position:absolute;top:calc((100% - var(--dock-h))/2);transform:translateY(-50%);z-index:4;
  width:2.9rem;height:2.9rem;background:rgba(255,255,255,.78);border:1px solid var(--border);
  color:var(--ink);display:grid;place-items:center;
  backdrop-filter:blur(14px) saturate(1.4);border-radius:var(--radius-pill);
  box-shadow:var(--shadow-pill)}
.edge:hover{background:#fff;box-shadow:var(--shadow-raised)}
.edge:active{transform:translateY(-50%) scale(var(--press-scale))}
.edge-prev{left:clamp(.5rem,1.6vw,1rem)}
.edge-next{right:clamp(.5rem,1.6vw,1rem)}

/* ── The flat pattern view ─────────────────────────────────────────────── */
.flat{display:grid;place-items:center;padding:clamp(5rem,10vw,8rem) clamp(1rem,4vw,3rem)
  calc(var(--dock-h) + 1.6rem)}
.flat-stage{position:relative;display:grid;place-items:center;width:100%;height:100%}
#flat-c{image-rendering:pixelated;grid-area:1/1;border-radius:4px;
  box-shadow:var(--sh-2),0 0 0 1px rgba(29,27,22,.08)}
.flat-ov{position:relative;grid-area:1/1;touch-action:none}
.handle{position:absolute;border:1.5px dashed rgba(197,31,44,.8);border-radius:3px;cursor:grab;
  background:rgba(197,31,44,.07);pointer-events:auto}
.handle.on{border:1.5px solid var(--blue-primary);background:rgba(37,99,235,.10);
  box-shadow:0 0 0 1px rgba(255,255,255,.5)}
.handle.on::after{content:'';position:absolute;right:-4px;bottom:-4px;width:8px;height:8px;
  background:var(--blue-primary);border:1px solid #fff}
.handle:active{cursor:grabbing}
.flat-hint{position:absolute;left:0;right:0;bottom:calc(var(--dock-h) + 1rem);text-align:center;
  font-size:var(--fs-caption);color:var(--text-secondary);font-weight:var(--fw-medium);
  font-variant-numeric:tabular-nums lining-nums;pointer-events:none}

/* ═════ The dock ═══════════════════════════════════════════════════════════
   One decision at a time. The tab row is always visible; the body under it
   swaps. Nothing in here is a bordered plate. */
.dock{position:absolute;left:0;right:0;bottom:0;z-index:6;
  background:linear-gradient(180deg,rgba(255,255,255,.62),rgba(255,255,255,.86) 40%,
    rgba(255,255,255,.93));
  backdrop-filter:blur(22px) saturate(1.6);border-top:1px solid rgba(29,27,22,.07)}
.dock-in{max-width:var(--wrap);margin:0 auto;padding:0 clamp(1.2rem,4vw,2.6rem)}
.dock-steps{display:flex;align-items:center;gap:.2rem;height:3rem}
.dock-steps button{height:3rem;padding:0 1rem;color:var(--text-secondary);
  font-size:var(--fs-body-sm);font-weight:var(--fw-medium);position:relative;white-space:nowrap}
.dock-steps button i{font-style:normal;font-weight:var(--fw-semibold);
  font-size:var(--fs-overline);letter-spacing:var(--ls-overline);margin-right:.5rem;
  color:var(--text-secondary);font-variant-numeric:tabular-nums lining-nums}
.dock-steps button:hover{color:var(--ink)}
.dock-steps button.on{color:var(--blue-primary);font-weight:var(--fw-semibold)}
.dock-steps button.on i{color:var(--blue-primary)}
.dock-steps button.on::after{content:'';position:absolute;left:1rem;right:1rem;bottom:.55rem;
  height:2px;border-radius:2px;background:var(--blue-primary)}
.dock-sp{flex:1}
.dock-steps .dock-x{color:var(--text-secondary);font-size:var(--fs-body-sm);
  font-weight:var(--fw-medium);padding:0 .75rem;display:inline-flex;align-items:center;
  gap:var(--space-1)}
.dock-steps .dock-x:hover{color:var(--ink)}
.dock-steps .dock-x.on{color:var(--blue-primary)}
/* The step row sets height:3rem on its buttons, which is right for the tabs and
   wrong for a pill — it stretched the send button to the full row and it read as
   clipped. A Button keeps the system's own padding-derived height. */
.dock-steps .btn{margin-left:var(--space-2);height:auto;padding:9px 18px;
  font-size:var(--fs-body-sm)}
.dock-body{padding:.9rem 0 1.1rem;min-height:5.4rem}
.pane{display:none;gap:.6rem}
/* Wrap rather than scroll: a row of controls that runs off the right edge with
   no affordance is a row of controls nobody finds. The dock grows, and the
   stage's render box shrinks to match (--dock-h). */
.pane.on{display:flex;align-items:flex-start;flex-wrap:wrap;row-gap:.7rem;
  padding-bottom:.15rem}
.pane.on::-webkit-scrollbar{display:none}
/* Panel labels are overlines — the system's casing rule for field and stat
   labels. Marketing headlines stay sentence case. */
.pane-lbl{flex:none;align-self:center;font-size:var(--fs-overline);
  font-weight:var(--fw-medium);text-transform:uppercase;letter-spacing:var(--ls-overline);
  color:var(--text-secondary);margin-right:.8rem;max-width:7.5rem;line-height:1.45}

/* Shape tiles: the blank as a technical silhouette, the way a direction card
   draws it — not a photograph of one colorway pretending to be all of them. */
.shape{flex:none;width:7.6rem;padding:.7rem .5rem .6rem;background:var(--surface);
  border:1px solid var(--border);color:var(--text-secondary);display:grid;gap:.35rem;
  justify-items:center;border-radius:var(--radius-card);box-shadow:var(--shadow-card)}
.shape svg{width:2.9rem;height:2.9rem;display:block;stroke:currentColor;fill:none;stroke-width:1.6;
  stroke-linejoin:round;stroke-linecap:round}
.shape b{font-size:var(--fs-body-sm);font-weight:var(--fw-semibold)}
.shape span{font-size:var(--fs-caption);color:var(--text-secondary);
  font-variant-numeric:tabular-nums lining-nums}
.shape:hover{color:var(--ink);box-shadow:var(--sh-2);transform:translateY(-2px)}
.shape.on{border-color:var(--blue-primary);background:var(--blue-tint);color:var(--blue-primary);
  box-shadow:var(--shadow-card)}
.shape.on span{color:var(--mid)}

/* Looks: real knitted swatches. */
.look{flex:none;width:5.4rem;display:grid;gap:.35rem;justify-items:center;padding:.25rem;
  border:1px solid transparent;border-radius:var(--r)}
.look canvas{width:5rem;height:auto;display:block;image-rendering:pixelated;border-radius:5px;
  box-shadow:var(--sh-1),0 0 0 1px rgba(29,27,22,.08);transition:transform var(--dur-base) var(--ease-out)}
.look b{font-size:var(--fs-caption);font-weight:var(--fw-medium);color:var(--text-secondary);
  text-align:center;line-height:1.35;
  min-height:2.5em;display:flex;align-items:flex-start;justify-content:center}
.look:hover b{color:var(--ink)}
.look:hover canvas{transform:translateY(-2px) scale(1.03)}
.look.on{border-color:transparent}
.look.on canvas{box-shadow:0 0 0 2px var(--blue-primary),var(--shadow-card)}
.look.on b{color:var(--blue-primary);font-weight:var(--fw-semibold)}

/* Cones + chips, on the dark. */
.cones{display:flex;gap:1.1rem;flex:none;align-items:flex-start}
.cone{display:grid;gap:.35rem}
.cone-l{font-size:var(--fs-overline);font-weight:var(--fw-medium);text-transform:uppercase;
  letter-spacing:var(--ls-overline);color:var(--text-secondary)}
.swatches{display:flex;gap:.3rem}
/* ColorSwatch — a yarn, at chip radius. */
.sw{width:26px;height:26px;border-radius:var(--radius-chip);background:var(--sw);
  border:1px solid rgba(16,24,40,.14);box-shadow:var(--shadow-tile)}
.sw:hover{transform:translateY(-1px)}
.sw.on{box-shadow:var(--shadow-tile),0 0 0 2px var(--surface),0 0 0 3px var(--blue-primary)}
.chips{display:flex;gap:.3rem;flex-wrap:wrap}
/* Chip — Bingo!/components/forms/Chip.jsx: 13px, 7/14 padding, 8px radius,
   selected = blue tint + blue border + blue text at weight 600. */
.chip{border:1px solid var(--border);background:var(--surface);color:var(--ink);
  border-radius:var(--radius-chip);padding:7px 14px;font-size:13px;
  font-weight:var(--fw-medium);white-space:nowrap;
  transition:var(--t-feedback),transform var(--dur-fast) var(--ease-settle)}
.chip:hover{background:var(--muted)}
.chip.on{background:var(--blue-tint);color:var(--blue-primary);
  border-color:var(--blue-primary);font-weight:var(--fw-semibold)}

/* Your mark. */
/* The dashed Chip affordance the system uses for "+ Custom", at drop-zone size. */
.drop{flex:none;display:flex;align-items:center;gap:var(--space-2);padding:9px 14px;
  border:1px dashed var(--border);border-radius:var(--radius-chip);color:var(--text-secondary);
  font-size:13px;font-weight:var(--fw-medium);cursor:pointer;background:var(--surface)}
.drop:hover{border-color:var(--blue-primary);color:var(--blue-primary);background:var(--blue-tint)}
.drop b{font-weight:var(--fw-semibold);color:var(--ink)}
.mkbtn{flex:none}
.ellist{display:flex;gap:.4rem;flex:none}
.elrow{display:flex;align-items:center;gap:var(--space-2);border:1px solid var(--border);
  border-radius:var(--radius-chip);padding:6px 10px;background:var(--surface);cursor:pointer;
  font-size:13px;color:var(--ink);max-width:16rem;box-shadow:var(--shadow-card)}
.elrow.on{border-color:var(--blue-primary);background:var(--blue-tint);color:var(--blue-primary)}
.elk{font-size:var(--fs-overline);font-weight:var(--fw-medium);
  letter-spacing:var(--ls-overline);text-transform:uppercase;color:var(--text-secondary)}
.elname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:700}
.elsw{width:14px;height:14px;border-radius:4px;border:1px solid rgba(16,24,40,.14);flex:none}
/* IconButton — round, quiet until hovered. Removing is the one destructive
   action in the dock, so it is the one place red belongs. */
.elx{width:24px;height:24px;flex:none;border:1px solid var(--border);
  border-radius:var(--radius-pill);color:var(--text-secondary);display:grid;place-items:center;
  background:var(--surface)}
.elx:hover{color:var(--ink);background:var(--muted)}
.elx.danger:hover{color:var(--red);border-color:var(--red);background:var(--red-tint)}
.ic{display:block}
.ic-wrap{display:inline-flex;align-items:center;flex:none}
.mini{display:grid;gap:.3rem;flex:none;min-width:8rem;font-size:var(--fs-overline);
  font-weight:var(--fw-medium);text-transform:uppercase;letter-spacing:var(--ls-overline);
  color:var(--text-secondary)}
.mini b{color:var(--ink);font-size:var(--fs-caption);letter-spacing:0;text-transform:none;
  font-variant-numeric:tabular-nums lining-nums}
.mini.txt{min-width:11rem}
.mini input[type=text]{background:var(--paper-2);border:1px solid var(--line);color:var(--ink);
  min-height:2.3rem;padding:.35rem .6rem;font-size:.85rem;font-weight:600;box-shadow:var(--sh-in);
  border-radius:var(--r-sm)}
.mini input[type=text]:focus{border-color:var(--blue-primary);box-shadow:var(--ring-focus)}
input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:1.4rem;background:transparent}
input[type=range]::-webkit-slider-runnable-track{height:4px;border-radius:2px;background:var(--paper-in)}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:1.15rem;height:1.15rem;
  border-radius:50%;background:#fff;border:1px solid var(--line-2);box-shadow:var(--sh-1);
  margin-top:-.42rem}
input[type=range]::-moz-range-track{height:4px;border-radius:2px;background:var(--paper-in)}
input[type=range]::-moz-range-thumb{width:1.15rem;height:1.15rem;border:1px solid var(--line-2);
  border-radius:50%;background:#fff}

/* ── The two slide-up drawers: Fine-tune and Spec ──────────────────────── */
.sheet{position:absolute;left:0;right:0;bottom:100%;background:rgba(255,255,255,.96);
  backdrop-filter:blur(22px) saturate(1.5);border-top:1px solid rgba(29,27,22,.07);
  max-height:min(24rem,52svh);overflow:auto;
  transform:translateY(100%);opacity:0;pointer-events:none;
  transition:transform var(--dur-slow) var(--ease-out),opacity var(--dur-base) var(--ease-out)}
.sheet.on{transform:none;opacity:1;pointer-events:auto}
.sheet-in{max-width:var(--wrap);margin:0 auto;padding:1.4rem clamp(1.2rem,4vw,2.6rem) 1.6rem;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));gap:1.5rem 2rem}
.tune-grp{display:grid;gap:.55rem;align-content:start}
.tune-l{font-size:var(--fs-overline);font-weight:var(--fw-medium);text-transform:uppercase;
  letter-spacing:var(--ls-overline);color:var(--text-secondary)}
.zonebar{display:flex;gap:.4rem;flex-wrap:wrap}
.zt{border:1px solid var(--border);background:var(--surface);padding:.4rem;display:grid;
  gap:.35rem;justify-items:center;border-radius:var(--radius-card);min-width:4.4rem;
  box-shadow:var(--shadow-card)}
.zt-art{display:grid;place-items:center;height:44px;min-width:2rem;overflow:hidden;
  border-radius:3px;background:var(--paper-3)}
.zt-art canvas{height:44px;width:auto;display:block;image-rendering:pixelated}
.zt-art.flat-fill{width:2.4rem;box-shadow:inset 0 2px 6px rgba(0,0,0,.4)}
.zt-l{font-size:var(--fs-caption);font-weight:var(--fw-medium);color:var(--text-secondary);
  white-space:nowrap}
.zt.on{border-color:var(--blue-primary);box-shadow:var(--shadow-card)}
.zt.on .zt-l{color:var(--ink)}
.spec{display:grid;grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));gap:0 2.2rem}
/* Spec rows live inside a sheet, so they take the dotted rule. */
.spec-i{display:flex;justify-content:space-between;gap:var(--space-4);padding:8px 0;
  font-size:var(--fs-body-sm);color:var(--text-secondary);
  background:var(--divider-dotted);background-position:left bottom}
.spec-i span{color:var(--dim);white-space:nowrap}
.spec-i b{text-align:right;color:var(--ink);font-weight:var(--fw-semibold);
  font-variant-numeric:tabular-nums lining-nums}
.spec-i:last-child{background:none}
.pom{width:100%;border-collapse:collapse;font-size:.8rem;color:var(--mid)}
.pom th{text-align:left;font-size:var(--fs-overline);font-weight:var(--fw-medium);
  text-transform:uppercase;letter-spacing:var(--ls-overline);color:var(--text-secondary);
  padding:.2rem .5rem .35rem 0;border-bottom:1px solid var(--border);vertical-align:bottom;
  line-height:1.4}
.pom td{padding:.4rem .5rem .4rem 0;font-weight:600;font-variant-numeric:tabular-nums;
  color:var(--ink)}

/* ── Narrow ────────────────────────────────────────────────────────────── */
@media (max-width:900px){
  .ov-tl{max-width:70%}
  .ov-tl h1{font-size:clamp(1.6rem,6vw,2.2rem)}
  .ov-tl p{display:none}
}
@media (max-width:760px){
  .studio{height:100svh;max-height:none;--dock-h:11.5rem}
  /* On a phone the title and the toolbar were sitting on top of each other in
     the same corner-to-corner band. Give the toolbar the top line to itself and
     drop the title under it. */
  .ov-tl{max-width:82%;top:4.1rem}
  .ov-tl .eyebrow{display:none}
  /* Everything has to fit on ONE line up here, or it wraps down into the title.
     The turntable button is the one that goes: taking hold of the cloth already
     stops it, and so does asking for a named view. */
  .ov-tr{gap:.25rem;top:.8rem;flex-wrap:nowrap}
  /* On a phone the overlay controls keep their icons and drop their labels —
     an IconButton is a shape the system already has. */
  .gbtn{padding:0 .6rem}
  .gbtn,.gseg button{height:2.1rem;padding:0 .5rem;font-size:.67rem;letter-spacing:.02em}
  /* seven controls do not fit across a phone at full width, and the two that
     shorten without going cryptic are the view names */
  .gbtn,.gseg button{height:2.2rem;padding:0 .6rem;font-size:.7rem}
  .edge{width:2.2rem;height:3rem;font-size:1.3rem}
  .dock-steps{height:2.8rem;overflow-x:auto;scrollbar-width:none}
  .dock-steps::-webkit-scrollbar{display:none}
  .dock-steps button{padding:0 .7rem;font-size:.74rem}
  .dock-steps .dock-x{padding:0 .5rem;font-size:.68rem}
  .dock-body{min-height:6.4rem}
  .pane.on{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none}
  .pane.on::-webkit-scrollbar{display:none}
  .sheet{max-height:60svh}
  .sheet-in{grid-template-columns:minmax(0,1fr)}
}

/* ── The film layer ───────────────────────────────────────────────────────
   A pointer, and controls that exist only while the pointer is on them. This is
   what "show function" looks like without putting a bar of buttons in front of
   the cloth: each affordance fades in under the cursor, gets used, and fades
   out behind it. Nothing in here is interactive — it is all pointer-events:none
   over the opener, which is the one real click target on the whole stage. */
.film-layer{position:absolute;inset:0;z-index:8;pointer-events:none}
.studio:not(.is-film) .film-layer{display:none}

.fcursor{position:absolute;width:26px;height:26px;margin:-3px 0 0 -3px;
  filter:drop-shadow(0 3px 6px rgba(16,24,40,.35));
  transition:transform var(--dur-instant) var(--ease-out),
             opacity var(--dur-slow) var(--ease-out)}
/* Nobody is working during the walk back through the finished pieces, so the
   hand leaves rather than hovering over cloth it is not touching. */
.fcursor.is-away{opacity:0}
.fcursor.is-down{transform:scale(.86)}
.fcursor.is-drag{transform:scale(.94)}
.fring{position:absolute;left:2px;top:2px;width:22px;height:22px;border-radius:50%;
  border:2px solid var(--blue-primary);opacity:0;transform:scale(.3)}
.fcursor.is-ring .fring{animation:fring 420ms var(--ease-out)}
@keyframes fring{
  0%{opacity:.9;transform:scale(.3)}
  100%{opacity:0;transform:scale(2.4)}
}

.fghost{position:absolute;transform:translate(-50%,-50%) scale(.96);opacity:0;
  transition:opacity var(--dur-base) var(--ease-out),transform var(--dur-base) var(--ease-out)}
.fghost.in{opacity:1;transform:translate(-50%,-50%) scale(1)}
/* A floating panel of the real thing: same surface, same radius, same shadow. */
.fpanel{display:flex;align-items:center;gap:var(--space-3);
  background:rgba(255,255,255,.92);border:1px solid var(--border);
  border-radius:var(--radius-card);padding:10px 14px;box-shadow:var(--shadow-menu);
  backdrop-filter:blur(16px) saturate(1.4);white-space:nowrap}
.flabel{font-size:var(--fs-overline);font-weight:var(--fw-medium);text-transform:uppercase;
  letter-spacing:var(--ls-overline);color:var(--text-secondary)}
.fswrow{display:flex;gap:6px}
/* .sw in the app: 30px, 8px radius, and the selected one takes a 2px blue
   outline at 2px offset. */
.fsw{width:30px;height:30px;border-radius:8px;
  border:1px solid rgba(0,0,0,.12);display:block;
  transition:outline-color var(--dur-fast) var(--ease-out)}
.fsw.on{outline:2px solid var(--blue-primary);outline-offset:2px}
.fbtn{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--fs-body-sm);
  font-weight:var(--fw-semibold);color:var(--ink);
  transition:color var(--dur-fast) var(--ease-out)}
.fbtn.is-hit{color:var(--blue-primary)}
.fchips{display:flex;gap:6px;flex-wrap:wrap;justify-content:center}
.fchip{font-size:13px;font-weight:var(--fw-medium);padding:6px 12px;
  border-radius:var(--radius-chip);border:1px solid var(--border);color:var(--ink);
  background:var(--surface);transition:var(--t-feedback)}
.fchip.on{background:var(--blue-tint);color:var(--blue-primary);border-color:var(--blue-primary);
  font-weight:var(--fw-semibold)}

/* The upload, filling. */
.fup{display:flex;align-items:center;gap:var(--space-3)}
.fup-ic{display:grid;place-items:center;width:34px;height:34px;border-radius:var(--radius-tile);
  background:var(--blue-tint);color:var(--blue-primary);flex:none}
.fup-body{display:grid;gap:5px;min-width:13rem}
.fup-body b{font-size:13px;font-weight:var(--fw-semibold)}
.fup-track{height:4px;border-radius:2px;background:var(--muted);overflow:hidden}
.fup-bar{display:block;height:100%;width:0;border-radius:2px;background:var(--blue-primary);
  transition:width 700ms var(--ease-out)}
.fup-bar.go{width:100%}
.fup-done{display:inline-flex;align-items:center;gap:5px;font-size:var(--fs-caption);
  color:var(--green);font-weight:var(--fw-medium)}

/* Typing. */
.ffield{display:inline-flex;align-items:center;gap:2px;background:var(--surface);
  border:1px solid var(--blue-primary);box-shadow:var(--ring-focus);
  border-radius:var(--radius-input);padding:7px 11px;min-width:9rem}
.ftext{font-size:var(--fs-body-sm);font-weight:var(--fw-semibold);letter-spacing:.02em}
.fcaret{width:1.5px;height:15px;background:var(--ink);animation:fcaret 1s steps(1) infinite}
@keyframes fcaret{0%,49%{opacity:1}50%,100%{opacity:0}}

/* The selection round the placed mark. Approximate on a turning garment — it is
   a gesture, not a hit box — so it stays quiet and only appears while a hand is
   actually on it. */
.fmark{--fsel-scale:1}
/* The studio outlines the selected graphic with `outline:1.5px dashed
   var(--blue-primary); outline-offset:3px` and gives it no handles at all. El,
   with the real inspector open: "theres no blue sizing around the reshape for
   graphics - its a slider." So this is the outline and only the outline; the
   Scale slider does the resizing. */
.fsel{display:block;width:clamp(8rem,17.5vw,15rem);aspect-ratio:1.71;
  transform:scale(var(--fsel-scale));
  outline:1.5px dashed rgba(37,99,235,.85);outline-offset:3px;border-radius:2px;
  background:rgba(37,99,235,.05)}

/* ── The studio's own controls, floating ──────────────────────────────────
   These are transcriptions, not inventions: the labels, the order and the
   shapes come from studio/index.html. A visitor watching the film and then
   opening the tool has to find the same things in the same words. */
.fstack{display:flex;flex-direction:column;gap:9px;align-items:stretch;
  background:rgba(255,255,255,.94);border:1px solid var(--border);
  border-radius:var(--radius-card);padding:12px 14px;box-shadow:var(--shadow-menu);
  backdrop-filter:blur(16px) saturate(1.4);min-width:min(20rem,74vw)}
.fhead{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-3)}
.fhead b{font-size:13px;font-weight:var(--fw-semibold);
  font-variant-numeric:tabular-nums lining-nums}
.fnote{font-size:11px;line-height:1.5;color:var(--text-secondary)}

/* Add a graphic — drop or browse, with the two thin buttons under it. */
.fdrop{display:flex;flex-direction:column;gap:2px;align-items:center;text-align:center;
  border:1.5px dashed var(--border);border-radius:var(--radius-card);padding:11px 14px;
  transition:border-color var(--dur-fast) var(--ease-out),
             background-color var(--dur-fast) var(--ease-out)}
.fdrop b{display:inline-flex;align-items:center;gap:6px;font-size:13px;
  font-weight:var(--fw-semibold);color:var(--ink)}
.fdrop small{font-size:11px;color:var(--text-secondary)}
.fdrop.is-hit{border-color:var(--blue-primary);background:var(--blue-tint)}
.frow2{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.frow3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px}
.fbtn2{display:inline-flex;align-items:center;justify-content:center;gap:5px;
  border:1px solid var(--border);border-radius:8px;padding:7px 9px;background:var(--surface);
  font-size:12px;font-weight:var(--fw-semibold);color:var(--ink);white-space:nowrap;
  transition:var(--t-feedback)}
.fbtn2.is-hit{border-color:var(--blue-primary);background:var(--blue-tint);
  color:var(--blue-primary)}
/* the repeat buttons run the full width of the block, as they do in the app */
.fbtn2.fwide{width:100%;justify-content:flex-start;padding:8px 10px}

/* The font grid: a big sample in the face, its name underneath. */
.ffaces{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
.fface{display:flex;flex-direction:column;align-items:center;gap:3px;
  border:1px solid var(--border);border-radius:8px;padding:7px 4px;background:var(--surface);
  transition:var(--t-feedback)}
.fface b{line-height:1;color:var(--ink)}
.fface small{font-size:10px;color:var(--text-secondary)}
.fface.on{border-color:var(--blue-primary);background:var(--blue-tint)}
.fface.on small{color:var(--blue-primary);font-weight:var(--fw-semibold)}

/* Scale. A real range control: track, fill, knob — the knob is where the
   cursor goes, and the percentage above it counts while it moves. */
.fsl-track{position:relative;height:4px;border-radius:2px;background:var(--muted);
  margin:5px 7px 9px}
.fsl-fill{position:absolute;left:0;top:0;height:100%;border-radius:2px;
  background:var(--blue-primary)}
.fsl-knob{position:absolute;top:50%;width:15px;height:15px;border-radius:50%;
  margin:-7.5px 0 0 -7.5px;background:var(--blue-primary);border:2px solid #fff;
  box-shadow:0 1px 3px rgba(16,24,40,.28)}

/* The green Digitized block, and the artwork-color-to-yarn row inside it. */
.fdig{display:flex;flex-direction:column;gap:7px;background:var(--green-tint,#EAF6EF);
  border-radius:8px;padding:10px 11px}
.fdig-h{display:flex;align-items:center;gap:8px}
.fdig-h b{font-size:12px;font-weight:var(--fw-semibold);color:var(--green)}
.fdig-h span{margin-left:auto;font-size:12px;color:var(--text-secondary);
  font-variant-numeric:tabular-nums lining-nums}
.fdet{display:flex;align-items:center;gap:7px}
/* .det-sw, .ysw and their states are the app's own rules, copied: 22px rounded
   artwork chip, 19px round yarn buttons, a 2px blue outline on the chosen one,
   .22 on a yarn you cannot have. */
.fdet-sw{width:22px;height:22px;border-radius:6px;flex:none;
  border:1px solid rgba(0,0,0,.12)}
.fdet-ar{font-size:11px;color:var(--text-secondary);flex:none}
.yswrow{display:flex;gap:4px;flex-wrap:wrap}
.ysw{width:19px;height:19px;border-radius:50%;display:block;
  border:1px solid rgba(0,0,0,.14);
  transition:outline-color var(--dur-fast) var(--ease-out),opacity var(--dur-fast) var(--ease-out)}
.ysw.on{outline:2px solid var(--blue-primary);outline-offset:1px}
/* the app dims a yarn your color budget cannot reach — same read here for a
   yarn that is not on the machine */
.ysw.off{opacity:.22}

@media (max-width:760px){
  .fpanel{padding:8px 11px;gap:var(--space-2)}
  .fsw{width:25px;height:25px}
  .fup-body{min-width:9rem}
  .fstack{padding:10px 11px;gap:7px;min-width:min(17rem,86vw)}
  .fdrop small{display:none}
}

/* ── The machine bar ──/* ── The machine bar ──────────────────────────────────────────────────────
   The readout, a scrubber, and a way to run it again — one row above the dock.
   The knit-in was an intro that played once and stopped; this makes it the
   interface. Drag it and you are working the machine: forward knits, backward
   unknits, and the count under your finger is real needles and real rows. */
.machine{position:absolute;left:clamp(1.2rem,4vw,2.6rem);right:clamp(1.2rem,4vw,2.6rem);
  bottom:calc(var(--dock-h) + .85rem);z-index:5;display:flex;align-items:center;gap:.9rem}
.machine .readout{flex:none}
.machine .readout.by-hand .dot{animation:none;background:var(--ink)}
.machine input[type=range]{flex:1;height:1.6rem;min-width:6rem}
.machine input[type=range]::-webkit-slider-runnable-track{height:4px;border-radius:2px;
  background:rgba(29,27,22,.12)}
.machine input[type=range]::-webkit-slider-thumb{width:1.1rem;height:1.1rem;border-radius:50%;
  background:var(--blue-primary);border:2px solid #fff;margin-top:-.42rem;
  box-shadow:0 1px 3px rgba(29,27,22,.25)}
.machine input[type=range]::-moz-range-track{height:4px;border-radius:2px;background:rgba(29,27,22,.12)}
.machine input[type=range]::-moz-range-thumb{width:1.1rem;height:1.1rem;border:2px solid #fff;
  border-radius:50%;background:var(--blue-primary)}
.machine .gbtn{flex:none}
@media (max-width:760px){
  .machine{gap:.5rem;left:.9rem;right:.9rem}
  /* the caption can run long — let it truncate rather than push Again off the
     right-hand edge */
  .machine .readout{font-size:.7rem;padding:.4rem .7rem;gap:.4rem;max-width:56%;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block;line-height:1.5}
  .machine .readout .dot{display:none}
  .machine .gbtn{padding:0 .7rem;font-size:.72rem}
}

/* ── The film ─────────────────────────────────────────────────────────────
   While the demo is running there is no chrome at all: no dock, no toolbar, no
   scrubber, no arrows. One garment, one line of type, and a single invitation.
   Everything below is what `.is-film` takes away; `?tool=1` puts it back. */
/* ⚠ The baby blanket is not in the film. El: "keep the baby blanket out of
   this." It is a real blank and it stays in the deck for the tool — hidden
   rather than removed, because the deck's scroll positions are index-based and
   taking a slide out of the flow would shift every one after it. */
.studio.is-film .slide[data-style="babyblanket"]{visibility:hidden}
.studio.is-film .dock,
.studio.is-film .ov-tr,
.studio.is-film .machine,
.studio.is-film .edge{display:none}
.studio.is-film{--dock-h:0px;cursor:pointer}

/* The whole stage is the click target — the button is the stage. The label
   rides at the bottom where it cannot cover the cloth, and lifts on hover so
   the page feels like it is offering something rather than waiting. */
.opener{position:absolute;inset:0;z-index:7;background:none;border:0;padding:0;
  display:flex;align-items:flex-end;justify-content:center;
  padding-bottom:clamp(1.6rem,4vh,3rem);cursor:pointer}
.studio:not(.is-film) .opener{display:none}
.opener-in{display:inline-flex;align-items:center;gap:var(--space-2);
  background:var(--blue-primary);color:#fff;border-radius:var(--radius-pill);
  padding:12px 22px;font-size:var(--fs-body);font-weight:var(--fw-semibold);
  box-shadow:var(--shadow-raised);
  transition:transform var(--dur-base) var(--ease-out),box-shadow var(--dur-base) var(--ease-out),
             background-color var(--dur-fast) var(--ease-out)}
.opener:hover .opener-in{background:var(--blue-primary-hover);transform:translateY(-2px);
  box-shadow:var(--shadow-menu)}
.opener:active .opener-in{transform:scale(var(--press-scale))}
.opener-ic{display:inline-flex;align-items:center}

/* The caption keeps working — it is the only thing narrating the film — but it
   sits quietly under the title rather than in a chip at the bottom. */
.studio.is-film .ov-tl{max-width:min(34rem,60%)}
/* The tool hides the blurb and the eyebrow on small screens to make room for
   its toolbar; the film has no toolbar and wants both (El, from an iPhone:
   "the subtitle isnt showing"). */
@media (max-width:900px){ .studio.is-film .ov-tl p{display:block;font-size:.95rem;max-width:24em} }
@media (max-width:760px){ .studio.is-film .ov-tl .eyebrow{display:inline-block} .studio.is-film .ov-tl p{font-size:.9rem} }

/* ── The first screen's entrance ───────────────────────────────────────────
   No header yet, so the wordmark sits at the top of the copy while the page is
   at the top and hands over to the header as it pops down. The headline TYPES
   in (El: "the text should maybe type in?"), then the eyebrow row and the blurb
   rise into place, then the invitation, then the scroll cue starts bobbing.
   `.intro` = typing in progress, `.intro-done` = everything landed. */
.hero-mark{display:block;height:1.9rem;width:auto;margin-bottom:1.15rem;opacity:0;transform:translateY(-6px);
  transition:opacity var(--dur-reveal) var(--ease-out),transform var(--dur-reveal) var(--ease-out)}
.ov-tl.intro .hero-mark,.ov-tl.intro-done .hero-mark{opacity:1;transform:none}
body:not(.is-top) .hero-mark{opacity:0 !important;transform:translateY(-6px) !important}
.ov-tl .eyebrow-row,.ov-tl #st-blurb{transition:opacity var(--dur-reveal) var(--ease-out),transform var(--dur-reveal) var(--ease-out)}
.ov-tl.intro .eyebrow-row,.ov-tl.intro #st-blurb{opacity:0;transform:translateY(10px)}
.ov-tl.intro-done .eyebrow-row{transition-delay:60ms}
.ov-tl.intro-done #st-blurb{transition-delay:160ms}
#st-title.typing::after{content:'';display:inline-block;width:3px;height:.82em;background:var(--ink);
  margin-left:.06em;vertical-align:-.06em;border-radius:1px;animation:fcaret 1s steps(1) infinite}
/* the blurb swaps with a small dip when the deck moves to another blank */
#st-blurb.swap{opacity:0;transform:translateY(6px);transition-duration:var(--dur-fast)}

/* a column now (pill over cue), so the axes swap: centre across, sit at the bottom */
.studio.is-film .opener{flex-direction:column;align-items:center;justify-content:flex-end;gap:.9rem}
/* Desktop: the pill sits under the subtitle in the copy block; the bottom one
   is gone and only the scroll cue stays down there. Phones keep the bottom pill
   (the copy block is tighter there) and lose the cue. */
.opener-copy{display:none;margin-top:1.4rem;cursor:pointer}
@media (min-width:901px){
  .studio.is-film .opener > .opener-in{display:none}
  .studio.is-film .opener-copy{display:inline-flex;opacity:0;transform:translateY(14px)}
  .studio.is-film.intro-done .opener-copy{opacity:1;transform:none;
    transition:opacity var(--dur-reveal) var(--ease-out) 300ms,transform var(--dur-reveal) var(--ease-out) 300ms}
  .studio.is-film.intro-done .ov-tl:hover .opener-copy{transition-delay:0ms;background:var(--blue-primary-hover)}
}
@media (max-width:760px){
  .studio.is-film .scroll-cue{display:none}
  .studio.is-film .opener{padding-bottom:1rem}
  /* the film's floating controls hang from a point near the bottom instead of
     centring on it, so the sweater stays fully in view above them */
  .fghost.fstack{transform:translate(-50%,-100%) scale(.96)}
  .fghost.fstack.in{transform:translate(-50%,-100%) scale(1)}
}
.studio.is-film .opener-in{opacity:0;transform:translateY(14px)}
.studio.is-film.intro-done .opener-in{opacity:1;transform:none;
  transition:opacity var(--dur-reveal) var(--ease-out) 320ms,transform var(--dur-reveal) var(--ease-out) 320ms}
.studio.is-film.intro-done .opener:hover .opener-in{transition-delay:0ms;transform:translateY(-2px)}
.scroll-cue{display:block;width:22px;height:22px;color:var(--text-secondary);opacity:0;
  transition:opacity var(--dur-reveal) var(--ease-out) 900ms}
.studio.is-film.intro-done .scroll-cue{opacity:.7;animation:bob 1.7s var(--ease-in-out) 1.2s infinite}
body:not(.is-top) .scroll-cue{opacity:0 !important;animation:none !important}
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(6px)}}
@media (prefers-reduced-motion:reduce){
  .scroll-cue{animation:none !important}
  #st-title.typing::after{display:none}
}

/* ── The close-up ─────────────────────────────────────────────────────────
   The macro shot fills the stage with cloth, which puts navy behind the
   headline and the caption and makes both unreadable. So while the camera is
   in that close, the copy gets a scrim: a soft wash of the page's own ground
   that fades out to nothing before it reaches the middle of the frame. The
   words stay put and stay legible, and the texture still owns the shot. */
.studio.is-film .ov-tl::before{content:'';position:absolute;
  left:calc(-1*clamp(1.2rem,4vw,2.6rem) - 4rem);
  top:calc(-1*clamp(1.2rem,3vw,2rem) - 4rem);
  width:calc(100% + clamp(1.2rem,4vw,2.6rem) + 22rem);
  height:calc(100% + 14rem);
  pointer-events:none;z-index:-1;opacity:0;
  background:linear-gradient(90deg,
    rgba(248,248,247,.98) 0%, rgba(248,248,247,.96) 46%,
    rgba(248,248,247,.62) 68%, rgba(248,248,247,0) 88%);
  -webkit-mask-image:linear-gradient(180deg,#000 0%,#000 52%,rgba(0,0,0,.45) 74%,transparent 94%);
  mask-image:linear-gradient(180deg,#000 0%,#000 52%,rgba(0,0,0,.45) 74%,transparent 94%);
  transition:opacity var(--dur-slow) var(--ease-out)}
.studio.is-film.is-macro .ov-tl::before{opacity:1}
.studio.is-film .ov-tl{isolation:isolate}
/* The caption rides the eyebrow line, after "Knitting Studio", rather than as
   its own row under the blurb (El: "make the status be the eyebrow now next to
   the knitting studio part"). Same scale as the eyebrow, its own case — a file
   name in caps would be wrong. */
.eyebrow-row{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;margin-bottom:.7rem}
.eyebrow-row .eyebrow{margin-bottom:0}
.film-say{display:inline-flex;align-items:center;gap:var(--space-2);
  font-size:var(--fs-caption);font-weight:var(--fw-medium);color:var(--text-secondary);
  font-variant-numeric:tabular-nums lining-nums;min-height:1.4em}
.film-say .dot{width:7px;height:7px;border-radius:50%;background:var(--blue-primary);flex:none;
  animation:pulse 1.15s var(--ease-in-out) infinite}
.film-say.is-done .dot{background:var(--green);animation:none}
.studio:not(.is-film) .film-say{display:none}
@media (max-width:760px){
  .studio.is-film .ov-tl{max-width:82%;top:1.4rem}
  .opener-in{padding:11px 18px;font-size:var(--fs-body-sm)}
}

/* The selection outline is now only ever drawn while a graphic is being
   DRAGGED, which is the one place the studio draws it too, so it never has to
   grow — `--fsel-scale` stays at 1 and the text scene has no box at all. */
