/* progress.css — The busy veil, the preparation overlay and the run log.
   Cascade position 16 of 18. Order is load-bearing and is asserted by
   verify-fixtures.mjs; index.html links these explicitly (never @import) because Caddy's
   try_files turns a missing file into a 200 text/html SPA shell.
   Owner after Step 1: section (l). */
/* The intake veil stays deliberately small. During preparation, the same layer
   becomes a transparent instrument panel so the actual model remains visible. */
.busy {
  position: absolute; inset: 0; z-index: 6; display: none; place-content: center; justify-items: center; gap: 12px;
  background: rgb(var(--sn-surface-0-rgb) / 0.7019607843137254); backdrop-filter: blur(3px); text-align: center;
}
.busy.on { display: grid; }
.busy__intake { display: grid; justify-items: center; gap: 12px; }
/* .spin and @keyframes spin moved to css/base.css — one spinner in the app (item 92). */
.busy p { font-size: 14px; color: var(--dim); }
.busy.is-prepare {
  display: block; background: linear-gradient(180deg, rgb(var(--sn-surface-0-rgb) / 0.3607843137254902), transparent 28%, transparent 70%, rgb(var(--sn-surface-0-rgb) / 0.5607843137254902));
  backdrop-filter: none; pointer-events: none; overflow: hidden; text-align: left;
}
.busy.is-prepare .busy__intake { display: none; }
.work-caption {
  position: absolute; top: 20px; left: 20px; max-width: min(390px, calc(100% - 40px));
  padding: 12px 14px; border-radius: 11px; background: rgb(var(--sn-surface-1-rgb) / 0.9098039215686274);
  box-shadow: inset 0 0 0 1px var(--line); backdrop-filter: blur(12px);
}
.work-caption span { display: block; margin-bottom: 2px; color: var(--signal); font-size: 11.5px; font-weight: 700; }
.work-caption strong { display: block; font-size: 14px; font-weight: 600; }
.work-visual { position: absolute; inset: 0; overflow: hidden; }
.work-orbit {
  position: absolute; width: min(48vw, 520px); aspect-ratio: 1; left: 50%; top: 50%;
  border: 1px solid rgb(var(--sn-accent-rgb) / 0.16862745098039217); border-radius: 50%; opacity: 0; transform: translate(-50%, -50%) rotateX(68deg);
}
.work-orbit::before, .work-orbit::after {
  content: ''; position: absolute; border-radius: inherit; border: 1px solid rgb(var(--sn-accent-rgb) / 0.12156862745098039);
}
.work-orbit::before { inset: 9%; }
.work-orbit::after { inset: 22%; }
.slice-sweep {
  position: absolute; left: 8%; right: 8%; top: 24%; height: 2px; opacity: 0;
  background: linear-gradient(90deg, transparent, var(--signal) 20%, var(--sn-ok-bright) 50%, var(--signal) 80%, transparent);
  box-shadow: 0 0 18px rgb(var(--sn-accent-rgb) / 0.4);
}
.slice-sweep::after {
  content: ''; position: absolute; left: 10%; right: 10%; top: 1px; height: 90px;
  background: linear-gradient(180deg, rgb(var(--sn-accent-rgb) / 0.12156862745098039), transparent); clip-path: polygon(0 0, 100% 0, 92% 100%, 8% 100%);
}
.setting-traces { position: absolute; inset: 0; opacity: 0; }
.setting-traces i {
  position: absolute; left: 18%; width: 22%; height: 1px; background: linear-gradient(90deg, transparent, rgb(var(--sn-accent-rgb) / 0.43137254901960786));
  transform-origin: right center;
}
.setting-traces i:nth-child(1) { top: 37%; transform: rotate(12deg); }
.setting-traces i:nth-child(2) { top: 51%; left: 62%; transform: rotate(-10deg); }
.setting-traces i:nth-child(3) { top: 64%; width: 18%; transform: rotate(-8deg); }
.write-layers { position: absolute; left: 50%; bottom: 22%; width: 180px; height: 56px; opacity: 0; transform: translateX(-50%); }
.write-layers i {
  position: absolute; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, rgb(var(--sn-accent-rgb) / 0.6509803921568628), transparent);
}
.write-layers i:nth-child(1) { bottom: 0; }
.write-layers i:nth-child(2) { bottom: 14px; }
.write-layers i:nth-child(3) { bottom: 28px; }
.write-layers i:nth-child(4) { bottom: 42px; }
.busy[data-work-stage="analyse"] .work-orbit,
.busy[data-work-stage="orient"] .work-orbit { opacity: 1; }
.busy[data-work-stage="verify"] .slice-sweep { opacity: 1; animation: slice-pass 3.2s cubic-bezier(.45,0,.25,1) infinite; }
.busy[data-work-stage="settings"] .setting-traces { opacity: 1; }
.busy[data-work-stage="settings"] .setting-traces i { animation: trace-read 2.4s ease-in-out infinite; }
.busy[data-work-stage="settings"] .setting-traces i:nth-child(2) { animation-delay: .5s; }
.busy[data-work-stage="settings"] .setting-traces i:nth-child(3) { animation-delay: 1s; }
.busy[data-work-stage="write"] .write-layers { opacity: 1; }
.busy[data-work-stage="write"] .write-layers i { animation: layer-write 1.9s ease-in-out infinite; }
.busy[data-work-stage="write"] .write-layers i:nth-child(2) { animation-delay: .2s; }
.busy[data-work-stage="write"] .write-layers i:nth-child(3) { animation-delay: .4s; }
.busy[data-work-stage="write"] .write-layers i:nth-child(4) { animation-delay: .6s; }
@keyframes slice-pass { 0% { transform: translateY(-70px); opacity: 0; } 15%, 85% { opacity: 1; } 100% { transform: translateY(320px); opacity: 0; } }
@keyframes trace-read { 0%, 100% { transform: scaleX(.2); opacity: .25; } 50% { transform: scaleX(1); opacity: 1; } }
@keyframes layer-write { 0%, 100% { transform: scaleX(.2); opacity: .25; } 50% { transform: scaleX(1); opacity: 1; } }


/* ══ UI Quiet (DESIGN §4.3): preparing appends to the pane; it no longer covers it ══════
   The five data-prep-stage rows are the same rows js/progress.js has always driven. They
   now sit in the pane's own scroll, under the decisions folded into one row, and only the
   rows the server has started are drawn: spinner -> tick, title, meta, duration. On the
   result the list folds to "Prepared in 6.3 s · 5 steps". Tokens only. */
.prep { display: none; gap: var(--sn-space-1); margin: 0 0 var(--sn-space-3); padding: 0 var(--sn-space-1); }
.prep.on,
html[data-route="preparing"] .prep,
html[data-route="result"] .prep { display: grid; }

/* The steps fold away while it runs and on the result, until "Change" opens them. */
html[data-route="preparing"] #paneBody > .step,
html[data-route="result"] #paneBody:has(> .prep:not(.is-unfolded)) > .step { display: none; }
/* "3 Prepare it" is not a step in the Quiet layout: the primary is in the toolbar. */
#step3 { display: none; }

.prep__fold,
.prep__run,
.prep-stage {
  display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; align-items: start;
  column-gap: var(--sn-space-3); padding: var(--sn-space-2) var(--sn-space-2) var(--sn-space-2) var(--sn-space-1);
}
.prep__fold-mark,
.prep__run-mark,
.prep-stage__mark {
  width: 24px; height: 24px; border-radius: var(--sn-radius-pill);
  display: grid; place-items: center; color: var(--sn-ink-dim); background: var(--sn-surface-2);
}
.prep__fold-mark svg,
.prep__run-mark svg { width: 12px; height: 12px; }
.prep__fold-copy { display: grid; gap: 2px; min-width: 0; }
.prep__fold-copy small { font-size: var(--sn-text-2xs); line-height: var(--sn-lh-2xs); color: var(--sn-ink-mute); }
.prep__fold-copy b { font-size: var(--sn-text-sm); line-height: var(--sn-lh-sm); font-weight: 500; color: var(--sn-ink); overflow-wrap: anywhere; }
.prep__fold-copy b:empty { display: none; }
.prep__change {
  align-self: center; min-height: 32px; padding: 0 var(--sn-space-1);
  color: var(--sn-ink-dim); font-size: var(--sn-text-xs); font-weight: 500; text-decoration: none;
}
.prep__change:hover { color: var(--sn-ink); filter: none; }
.prep__lock { margin: 0 0 var(--sn-space-1) calc(24px + var(--sn-space-3) + var(--sn-space-1)); font-size: var(--sn-text-xs); color: var(--sn-ink-mute); }

.prep__run {
  width: 100%; border: 0; background: none; text-align: left; cursor: pointer; color: var(--sn-ink-dim);
  border-radius: var(--sn-radius-md); font: inherit; font-size: var(--sn-text-sm); align-items: center;
  transition: background-color var(--sn-dur-fast) var(--sn-ease-out);
}
.prep__run:hover { background: var(--sn-surface-2); }
.prep__chev { width: 14px; height: 14px; color: var(--sn-ink-dim); transition: rotate var(--sn-dur-base) var(--sn-ease-out); }
.prep__run[aria-expanded="true"] .prep__chev { rotate: 90deg; }

.prep__stages { list-style: none; padding: 0; margin: 0; display: grid; gap: 0; }
/* Only what the server has started is drawn — the list APPENDS (DESIGN §4.3). */
.prep-stage:not(.is-active):not(.is-done):not(.is-skipped):not(.is-refused) { display: none; }
.prep-stage__mark { font-size: var(--sn-text-2xs); font-weight: 700; }
.prep-stage__mark > span { display: none; }
.prep-stage__copy { display: grid; gap: 2px; min-width: 0; padding-top: 2px; }
.prep-stage__copy b { font-size: var(--sn-text-sm); line-height: var(--sn-lh-sm); font-weight: 500; color: var(--sn-ink); }
.prep-stage__copy span:empty { display: none; }
.prep-stage__copy span { font-size: var(--sn-text-xs); line-height: var(--sn-lh-xs); color: var(--sn-ink-mute); overflow-wrap: anywhere; }
.prep-stage__time { padding-top: 5px; font-size: var(--sn-text-2xs); color: var(--sn-ink-mute); white-space: nowrap; }
/* Live: a ring spinner in the node, and the row's own words in full ink. */
.prep-stage.is-active .prep-stage__mark { background: none; box-shadow: inset 0 0 0 1.5px var(--sn-line-strong); }
.prep-stage.is-active .prep-stage__mark::after {
  content: ''; width: 24px; height: 24px; border-radius: var(--sn-radius-pill);
  border: 1.5px solid var(--sn-ink); border-color: var(--sn-ink) transparent transparent transparent;
  animation: spin 1s linear infinite;
}
/* Done: a tick in the node, and the title steps back to ink-soft. */
.prep-stage.is-done .prep-stage__mark::after { content: '✓'; font-size: var(--sn-text-2xs); color: var(--sn-ink); }
.prep-stage.is-done .prep-stage__copy b { color: var(--sn-ink-soft); }
/* Terminal but NOT done: a step the run did not perform. Never the tick — that mark means
   "we did this and it passed". */
.prep-stage.is-skipped .prep-stage__mark::after { content: '–'; font-size: var(--sn-text-xs); }
.prep-stage.is-skipped .prep-stage__copy b { color: var(--sn-ink-dim); }
/* Ran, and said no. Not a tick and not a dash: this step learned something. */
.prep-stage.is-refused .prep-stage__mark { color: var(--sn-warn); background: var(--sn-warn-tint); box-shadow: inset 0 0 0 1px var(--sn-warn-edge); }
.prep-stage.is-refused .prep-stage__mark::after { content: '✕'; font-size: var(--sn-text-2xs); }
.prep-stage.is-refused .prep-stage__copy b { color: var(--sn-warn); }

/* Tester view: everything the old takeover said, one quiet block under the list — once a
   run has happened on this page (a fixture or a reload has no run to describe). */
.prep:not(.has-run) .prep__tester { display: none; }
.prep__tester { display: grid; gap: var(--sn-space-2); margin-top: var(--sn-space-2); padding: var(--sn-space-3); border-radius: var(--sn-radius-md); background: var(--sn-surface-2); }
.prep__head { display: grid; grid-template-columns: 1fr auto; gap: var(--sn-space-1) var(--sn-space-3); align-items: baseline; }
.prep__head h2 { font-size: var(--sn-text-sm); line-height: var(--sn-lh-sm); font-weight: 600; }
.prep__head p { grid-column: 1 / -1; color: var(--sn-ink-dim); font-size: var(--sn-text-xs); }
.prep__elapsed { font-size: var(--sn-text-xs); color: var(--sn-ink-mute); }
.prep__insight span { display: block; color: var(--sn-ink-mute); font-size: var(--sn-text-2xs); font-weight: 600; margin-bottom: var(--sn-space-1); }
.prep__insight p { color: var(--sn-ink-soft); font-size: var(--sn-text-xs); line-height: var(--sn-lh-xs); }
.prep__truth { color: var(--sn-ink-mute); font-size: var(--sn-text-2xs); line-height: var(--sn-lh-2xs); }
.prep__truth b { color: var(--sn-ink-dim); font-weight: 600; }

/* The viewport during preparation (DESIGN §1.7: progress never covers the model). The
   ring card from js/viewport-ui.js is the visible progress now, so the old caption leaves
   the picture but NOT the accessibility tree: #busy is still the one live region that
   announces each stage (items 45, 78). The darkening veil and the orbit ring go; the
   verify stage keeps its green scan band (DESIGN §4.3 "a green scan band rises"). */
.busy.is-prepare { background: none; }
.busy.is-prepare .work-orbit,
.busy.is-prepare .setting-traces,
.busy.is-prepare .write-layers { display: none; }
.busy.is-prepare .work-caption {
  position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; box-shadow: none;
}

/* Draggable run log: live stages + the numbers a slicer operator actually reads. */
.runlog {
  display: none;
  position: fixed; z-index: 40; left: 20px; top: 72px;
  width: min(360px, calc(100vw - 24px));
  max-height: min(72vh, 640px);
  flex-direction: column;
  background: rgb(var(--sn-surface-1-rgb) / 0.9607843137254902); color: var(--ink);
  border-radius: 14px;
  box-shadow: inset 0 0 0 1px var(--line-strong), 0 18px 48px rgb(var(--sn-black-rgb) / 0.6274509803921569);
  overflow: hidden;
}
.runlog.on { display: flex; }
.runlog__bar {
  display: grid; grid-template-columns: 1fr auto auto; align-items: center; gap: 8px;
  padding: 10px 10px 10px 14px; cursor: grab; user-select: none;
  background: var(--surface); box-shadow: inset 0 -1px 0 var(--line);
  touch-action: none;
}
.runlog__bar:active { cursor: grabbing; }
.runlog__bar b { font-family: Gabarito, sans-serif; font-size: var(--sn-text-sm); letter-spacing: -.01em; }
.runlog__bar .runlog__elapsed {
  font-variant-numeric: tabular-nums; font-size: 12px; color: var(--dim);
}
.runlog__bar button {
  border: 0; background: none; color: var(--dim2); width: 28px; height: 28px;
  border-radius: 8px; font-size: 15px; line-height: 1;
}
.runlog__bar button:hover { color: var(--ink); background: rgb(var(--sn-white-rgb) / 0.0784313725490196); }
.runlog__nums {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--line);
  border-bottom: 1px solid var(--line);
  overflow-y: auto;
  max-height: min(52vh, 460px);
}
.runlog__nums:empty { display: none; }
.runlog__num {
  background: var(--panel); padding: 8px 10px; min-width: 0;
}
.runlog__num span { display: block; font-size: var(--sn-text-2xs); color: var(--dim2); margin-bottom: 2px; }
.runlog__num b {
  display: block; font-size: var(--sn-text-sm); font-weight: 700; font-family: Gabarito, sans-serif;
  letter-spacing: -.01em; line-height: 1.25; word-break: break-word;
}
.runlog__num.is-wide { grid-column: 1 / -1; }
.runlog__num.is-good b { color: var(--signal); }
.runlog__num.is-caution b { color: var(--warn); }
.runlog__num.is-poor b { color: var(--sn-bad-title); }
.runlog__num.is-good { box-shadow: inset 3px 0 0 var(--signal); }
.runlog__num.is-caution { box-shadow: inset 3px 0 0 var(--warn); }
.runlog__num.is-poor { box-shadow: inset 3px 0 0 var(--sn-bad-title); }
.runlog__shadow {
  display: none; padding: 8px 12px; font-size: 12px; line-height: 1.4; color: var(--dim);
  border-bottom: 1px solid var(--line); background: rgb(var(--sn-accent-rgb) / 0.047058823529411764);
}
.runlog__shadow.on { display: block; }
.runlog__shadow b { color: var(--signal); }
.runlog__shadow.is-warn { background: rgb(var(--sn-warn-rgb) / 0.07058823529411765); }
.runlog__shadow.is-warn b { color: var(--warn); }
.runlog__events {
  list-style: none; padding: 8px 0 10px; margin: 0; overflow-y: auto;
  min-height: 72px; flex: 1 1 auto;
}
.runlog__events li {
  display: grid; grid-template-columns: 64px 1fr; gap: 8px;
  padding: 4px 14px; font-size: 12.5px; line-height: 1.35;
}
.runlog__events time { color: var(--dim2); font-variant-numeric: tabular-nums; font-size: 11px; padding-top: 2px; }
.runlog__events li[data-kind="ok"] span { color: var(--signal); }
.runlog__events li[data-kind="warn"] span { color: var(--warn); }
.runlog__events li[data-kind="err"] span { color: var(--sn-bad-title); }
@media (max-width: 940px) {
  .runlog { left: 12px; right: 12px; width: auto; top: auto; bottom: 12px; max-height: 46vh; }
}
/* Stage audit S2 (24 Sep): css/shell.css floats the log over the viewport's right side,
   which is exactly where the gizmo (right 12, 84 wide), the camera column (right 38) and
   the stats line (top 12) live — MEASURED on staging, the open log covered all three.
   It now starts clear of them: 104 px in from the stage's right edge (the stats line's own
   inset) and below the stats line. `.runlog.on` (0,2,0) outranks shell.css's `.runlog`
   (0,1,0) regardless of file order; a drag still wins, it writes inline styles. Its
   bottom stops 200 px above the viewport's, clear of the toolbar and, in the tester view,
   the text tool row and the drag hint above it (measured at 1440 x 900). */
@media (min-width: 941px) {
  .runlog.on {
    left: auto; top: 116px;
    right: calc(var(--sn-rail-w) + var(--sn-space-2) * 2 + 104px);
    max-height: min(calc(100vh - 116px - 200px), 640px);
  }
}


@media (prefers-reduced-motion: reduce) {
  .busy.is-prepare .work-orbit, .slice-sweep, .setting-traces, .write-layers { opacity: 0 !important; }
  .prep-stage.is-active .prep-stage__mark::after { border-top-color: var(--signal); }
}

/* ══ item 67 — success is stated in the success palette ═══════════════════════════════
   `.notice`'s default tone in css/notices.css is amber, and that file reserves amber for
   "unchecked, a caveat, out of date". The success headline was wearing it: MEASURED
   rgb(245,217,180) on rgba(255,183,101,0.1) on a live prepare of benchy.stl at
   1440x900. js/progress.js is the only writer of that headline, so the ok tone lives
   next to its writer.

   THE PROPER HOME IS css/notices.css, which is frozen after Step 1 — the same three
   declarations as `.notice--verified` twenty lines above it, under a name that means
   "it worked" rather than "a slicer proved it worked". A contract line asks the
   orchestrator to fold both into one `.notice.is-ok` there and delete this block.
   Nothing here depends on cascade order: `.notice.notice--ok` is (0,2,0) against the
   default tone's (0,1,0). */
.notice.notice--ok {
  background: rgb(var(--sn-accent-rgb) / 0.10196078431372549);
  box-shadow: inset 0 0 0 1px rgb(var(--sn-accent-rgb) / 0.25098039215686274);
  color: var(--sn-ok-ink);
}
.notice.notice--ok b { color: var(--signal); }
