/* Bambu connect + confirm dialogs. Themed from the app's own variables so they
   match whatever theme is active. */
.bambuBackdrop{
  position:fixed;inset:0;
  /* The notification centre computes to z-index 2147483000 (a later sheet
     overrides its own rule), and this dialog is opened FROM a notification, so
     it has to clear that -- while staying under the welcome modal (…645) and
     the guided tour (…646). Measured, not assumed. */
  z-index:2147483400;
  display:grid;place-items:center;padding:18px;
  background:color-mix(in srgb,var(--bg) 55%,rgba(3,6,11,.78));
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
}
.bambuDialog{
  display:flex;flex-direction:column;
  width:min(520px,100%);max-height:88dvh;overflow:hidden;
  border:1px solid color-mix(in srgb,var(--accent) 42%,var(--line));
  border-radius:18px;
  background:color-mix(in srgb,var(--panel) 97%,black 3%);
  box-shadow:0 30px 80px rgba(0,0,0,.55);
}
.bambuHead{
  display:flex;align-items:center;gap:12px;
  padding:15px 16px;
  border-bottom:1px solid color-mix(in srgb,var(--accent) 16%,var(--line));
  background:linear-gradient(135deg,color-mix(in srgb,var(--accent) 10%,var(--panel)),var(--panel));
}
.bambuLogo{
  display:grid;place-items:center;flex:0 0 auto;width:40px;height:40px;
  border:1px solid color-mix(in srgb,var(--accent) 34%,var(--line));
  border-radius:11px;background:color-mix(in srgb,var(--accent) 8%,transparent);
}
.bambuLogo img{width:23px;height:23px;display:block}
.bambuHead>div{flex:1 1 auto;min-width:0}
.bambuHead span{color:var(--accent2,var(--accent));font-size:.58rem;font-weight:900;letter-spacing:.14em;text-transform:uppercase}
.bambuHead h2{margin:1px 0 0;font-size:1.02rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bambuClose{
  flex:0 0 auto;width:30px;height:30px;padding:0;cursor:pointer;
  border:1px solid color-mix(in srgb,var(--accent) 30%,var(--line));
  border-radius:9px;background:var(--surface-raised,var(--panel2));
  color:var(--muted);font-size:1.15rem;line-height:1;
}
.bambuClose:hover{color:var(--text);border-color:var(--accent)}
.bambuBody{padding:16px;overflow-y:auto;display:grid;gap:12px}
.bambuBody>p{margin:0;color:var(--muted);font-size:.84rem;line-height:1.5}
.bambuField{display:grid;gap:5px}
.bambuField>span{color:var(--muted);font-size:.65rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}
.bambuField textarea,.bambuField select{
  width:100%;padding:9px 11px;
  border:1px solid color-mix(in srgb,var(--line) 85%,transparent);
  border-radius:9px;background:var(--surface-input,var(--panel));
  color:var(--text);font-size:.82rem;font-family:inherit;resize:vertical;
}
.bambuField textarea:focus,.bambuField select:focus{
  outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent);
}
.bambuNote{
  margin:0;padding:10px 12px;
  border:1px solid color-mix(in srgb,var(--good,#4ade80) 34%,var(--line));
  border-radius:10px;
  background:color-mix(in srgb,var(--good,#4ade80) 8%,transparent);
  color:var(--muted);font-size:.74rem;line-height:1.5;
}
.bambuNote b{color:var(--text)}
.bambuError{
  margin:0;padding:9px 11px;border-radius:9px;
  border:1px solid color-mix(in srgb,var(--bad,#ef4444) 45%,var(--line));
  background:color-mix(in srgb,var(--bad,#ef4444) 10%,transparent);
  color:var(--text);font-size:.78rem;
}
.bambuError[hidden]{display:none}
.bambuRows{display:grid;gap:8px}
.bambuRow{
  display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:9px;
  padding:9px 10px;
  border:1px solid color-mix(in srgb,var(--line) 80%,transparent);
  border-radius:10px;background:color-mix(in srgb,var(--surface-input,var(--panel)) 84%,transparent);
}
.bambuChip{
  display:inline-flex;align-items:center;gap:7px;
  font-size:.74rem;font-weight:700;white-space:nowrap;
}
.bambuChip i{
  width:13px;height:13px;border-radius:50%;
  border:1px solid color-mix(in srgb,#fff 22%,transparent);
}
.bambuRow select{
  min-width:0;padding:7px 9px;
  border:1px solid color-mix(in srgb,var(--line) 85%,transparent);
  border-radius:8px;background:var(--surface-input,var(--panel));
  color:var(--text);font-size:.78rem;
}
/* Fixed width so the app's auto-injected number stepper (which adds ~48px of
   right padding and a control column) fits cleanly instead of being crammed
   into a 78px box. */
.bambuGrams{display:grid;gap:3px;justify-items:stretch;width:118px;flex:0 0 auto}
.bambuGrams span{color:var(--muted);font-size:.6rem;text-transform:uppercase;letter-spacing:.06em;white-space:nowrap;text-align:left}
.bambuGrams .numberStepper{width:100%}
.bambuGrams input{
  width:100%;padding:8px 10px;text-align:left;
  border:1px solid color-mix(in srgb,var(--line) 85%,transparent);
  border-radius:8px;background:var(--surface-input,var(--panel));
  color:var(--text);font-size:.82rem;
}
/* When enhanced, let the stepper own the right padding and sit snug. */
.bambuGrams .numberStepper>input[type="number"]{padding-right:42px!important}
.bambuGrams .numberStepperControls{right:5px;width:30px}
.bambuFoot{
  display:flex;align-items:center;justify-content:flex-end;gap:9px;
  padding:13px 16px;
  border-top:1px solid color-mix(in srgb,var(--accent) 15%,var(--line));
  background:var(--surface-raised,var(--panel2));
}
@media(max-width:560px){
  .bambuRow{grid-template-columns:1fr;gap:7px}
  .bambuGrams{width:100%}
  .bambuFoot{flex-direction:column-reverse}
  .bambuFoot .btn{width:100%}
}
.bambuPasteRow{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:-4px}
.bambuPaste{
  padding:6px 12px;cursor:pointer;white-space:nowrap;
  border:1px solid color-mix(in srgb,var(--accent) 40%,var(--line));
  border-radius:8px;background:color-mix(in srgb,var(--accent) 10%,var(--surface-raised,var(--panel2)));
  color:var(--text);font-size:.74rem;font-weight:700;
}
.bambuPaste:hover{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 18%,transparent)}
.bambuTokenInfo{font-size:.72rem;color:var(--muted);min-width:0;overflow:hidden;text-overflow:ellipsis}
.bambuTokenInfo.good{color:var(--good,#4ade80)}
.bambuTokenInfo.bad{color:var(--bad,#ef4444)}
/* ---- connect walkthrough ---- */
.bambuSteps{
  display:grid;gap:11px;margin:0;padding:0;list-style:none;
  counter-reset:bambu;
}
.bambuSteps li{display:grid;grid-template-columns:auto 1fr;gap:11px;align-items:start}
.bambuStepNo{
  display:grid;place-items:center;width:23px;height:23px;flex:0 0 auto;
  border-radius:50%;background:var(--accent);color:var(--accent-ink,#fff);
  font-size:.72rem;font-weight:900;
}
.bambuSteps b{display:block;font-size:.82rem;margin-bottom:2px}
.bambuSteps p{margin:0;color:var(--muted);font-size:.76rem;line-height:1.45}
.bambuSteps a{color:var(--accent2,var(--accent))}
.bambuSteps kbd{
  display:inline-block;padding:1px 6px;border-radius:5px;
  border:1px solid color-mix(in srgb,var(--line) 85%,transparent);
  background:var(--surface-raised,var(--panel2));
  font-family:inherit;font-size:.72rem;font-weight:700;
}
.bambuHint{margin-top:5px!important;font-size:.7rem!important;opacity:.85}
.bambuSteps .bambuPaste{margin-top:7px}
/* Simple diagram of where the Console tab sits, rather than a screenshot of
   someone else's product that would go stale. */
.bambuDiagram{display:block;margin-top:7px}
.bambuDiagram svg{width:100%;max-width:240px;height:auto;display:block}
.bambuDiagram rect,.bambuDiagram path,.bambuDiagram circle{
  fill:none;stroke:color-mix(in srgb,var(--line) 90%,transparent);stroke-width:1;
}
.bambuDiagram circle{fill:color-mix(in srgb,var(--line) 70%,transparent);stroke:none}
.bambuDiagram rect.hot{
  fill:color-mix(in srgb,var(--accent) 22%,transparent);
  stroke:var(--accent);stroke-width:1.4;
}
.bambuDiagram text{font-size:8px;fill:var(--muted);stroke:none;font-family:inherit}
.bambuDiagram text.hot{fill:var(--accent2,var(--accent));font-weight:800}
.bambuDiagram text.dim{opacity:.65}
.bambuSnippetBox{
  width:100%;min-height:64px;margin-top:7px;padding:8px 10px;
  border:1px solid var(--accent);border-radius:8px;
  background:var(--surface-input,var(--panel));color:var(--text);
  font-family:ui-monospace,Consolas,monospace;font-size:.68rem;
}
.bambuWarn{
  margin:0;padding:10px 12px;
  border:1px solid color-mix(in srgb,var(--warn,#f59e0b) 45%,var(--line));
  border-radius:10px;
  background:color-mix(in srgb,var(--warn,#f59e0b) 9%,transparent);
  color:var(--muted);font-size:.73rem;line-height:1.5;
}
.bambuWarn b{color:var(--text)}
.bambuField input{
  width:100%;padding:9px 11px;
  border:1px solid color-mix(in srgb,var(--line) 85%,transparent);
  border-radius:9px;background:var(--surface-input,var(--panel));
  color:var(--text);font-size:.85rem;font-family:inherit;
}
.bambuField input:focus{
  outline:none;border-color:var(--accent);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent);
}
[data-stage]{display:grid;gap:12px}
[data-stage][hidden]{display:none}
/* ---- connected-services dock (bottom-left, clear of AI chat / Complete Print) ---- */
.serviceDockSlot{display:inline-flex;position:relative}
/* Bottom-left, clear of the AI chat and Complete Print controls on the right.
   Services stack vertically; the collapse toggle sits at the bottom (nearest the
   corner) so the stack grows upward. */
.serviceDock{
  position:fixed;
  left:16px;
  bottom:16px;
  z-index:9500;
  display:flex;
  flex-direction:column-reverse;
  gap:9px;
  /* Left-align so the icons sit directly over the collapse (^) button rather than
     centering over the wider ^ + controls row. */
  align-items:flex-start;
}
/* Cloud storage rides on top of Bambu regardless of which script rendered first.
   In a column-reverse the LAST item paints on top, so higher order = higher up:
   Drive on top, then Dropbox, then Bambu at the bottom. */
.serviceDockSlot:has([data-service="bambu"]){order:1}
.serviceDockSlot:has([data-service="dropbox"]){order:2}
.serviceDockSlot:has([data-service="drive"]){order:3}
.serviceDockControls{order:0}
/* toggle (^) and add (+) share one row at the bottom of the dock */
.serviceDockControls{display:flex;align-items:center;gap:6px}
/* "Go to Drive/Dropbox" anchor styled as a button */
.serviceDockGo{text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px}
/* Reconnect CTA shown on the card when a connection needs re-authorising */
.serviceDockReconnect{width:100%;margin:2px 0 6px}
/* collapse control */
.serviceDockToggle{
  display:none;
  place-items:center;
  width:46px;height:26px;padding:0;cursor:pointer;
  border:1px solid color-mix(in srgb,var(--accent) 32%,var(--line));
  border-radius:999px;
  background:color-mix(in srgb,var(--panel) 92%,black 8%);
  color:var(--muted);
  box-shadow:0 6px 16px rgba(0,0,0,.3);
  transition:color .16s ease,border-color .16s ease;
}
.serviceDock.hasMany .serviceDockToggle{display:grid}
.serviceDockToggle:hover{color:var(--text);border-color:var(--accent)}
/* "+" add-workflow button, sits to the right of the ^ toggle */
.serviceDockAdd{
  display:grid;place-items:center;
  width:26px;height:26px;padding:0;cursor:pointer;
  border:1px solid color-mix(in srgb,var(--accent) 40%,var(--line));
  border-radius:999px;
  background:color-mix(in srgb,var(--panel) 92%,black 8%);
  color:var(--accent2,var(--accent));
  box-shadow:0 6px 16px rgba(0,0,0,.3);
  transition:color .16s ease,border-color .16s ease,background .16s ease,transform .16s ease;
}
.serviceDockAdd:hover{color:var(--text);border-color:var(--accent);background:color-mix(in srgb,var(--accent) 14%,var(--panel));transform:translateY(-1px)}
.serviceDockAdd svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}
/* connect-a-workflow menu (reuses the dock card shell) */
.serviceDockAddList{display:grid;gap:6px;margin:2px 0 4px}
.serviceDockAddItem{
  display:flex;align-items:center;gap:10px;width:100%;
  padding:9px 11px;border:1px solid var(--line);border-radius:12px;
  background:var(--surface-input,var(--panel));color:var(--text);cursor:pointer;text-align:left;
  transition:border-color .16s ease,background .16s ease;
}
.serviceDockAddItem:hover{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 10%,var(--panel))}
.serviceDockAddItem img{width:22px;height:22px;border-radius:6px;flex:0 0 auto}
.serviceDockAddItem span{flex:1 1 auto;font-weight:600;font-size:.82rem}
.serviceDockAddItem em{font-style:normal;font-size:.66rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--accent2,var(--accent))}
.serviceDockAddDone{color:var(--muted);font-size:.8rem;margin:2px 2px 6px}
.serviceDockToggle svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:transform .28s ease}
.serviceDock.collapsed .serviceDockToggle svg{transform:rotate(180deg)}
/* count + health dot on the toggle when collapsed */
.serviceDockSummary{display:none;align-items:center;gap:5px}
.serviceDockToggle b{font-size:.64rem;font-weight:900;color:var(--accent2,var(--accent))}
.serviceDockHealth{
  width:8px;height:8px;border-radius:50%;flex:0 0 auto;
  background:var(--good,#4ade80);
  box-shadow:0 0 6px color-mix(in srgb,var(--good,#4ade80) 70%,transparent);
}
.serviceDockHealth.bad{
  background:var(--warn,#f59e0b);
  box-shadow:0 0 6px color-mix(in srgb,var(--warn,#f59e0b) 70%,transparent);
}
.serviceDock.collapsed .serviceDockSummary{display:flex}
.serviceDock.collapsed .serviceDockToggle svg{display:none}
/* a detected Bambu job (or a problem) pulses the collapsed dock */
.serviceDock.alerting .serviceDockToggle{
  border-color:var(--accent);
  animation:serviceDockAlert 1.5s ease-in-out infinite;
}
.serviceDock.alerting .serviceDockHealth{
  background:var(--accent2,var(--accent));
  animation:serviceDockDotPulse 1.1s ease-in-out infinite;
}
@keyframes serviceDockAlert{
  0%,100%{box-shadow:0 6px 16px rgba(0,0,0,.3),0 0 0 0 color-mix(in srgb,var(--accent) 55%,transparent)}
  50%{box-shadow:0 6px 16px rgba(0,0,0,.3),0 0 0 8px color-mix(in srgb,var(--accent) 0%,transparent)}
}
@keyframes serviceDockDotPulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(1.3)}}
@media(prefers-reduced-motion:reduce){
  .serviceDock.alerting .serviceDockToggle,.serviceDock.alerting .serviceDockHealth{animation:none!important}
}
/* collapsed: slots animate out */
.serviceDockSlot{
  transition:opacity .24s ease,transform .24s ease,max-height .28s ease,margin .28s ease;
  transform-origin:bottom center;
}
.serviceDock.collapsed .serviceDockSlot{
  opacity:0;transform:scale(.4) translateY(14px);
  max-height:0;margin:-4px 0;pointer-events:none;
}
.serviceDockItem{
  position:relative;
  display:grid;place-items:center;
  width:46px;height:46px;padding:0;cursor:pointer;
  border:1px solid color-mix(in srgb,var(--good,#4ade80) 50%,var(--line));
  border-radius:14px;
  background:color-mix(in srgb,var(--panel) 94%,black 6%);
  box-shadow:0 8px 22px rgba(0,0,0,.35);
  transition:transform .18s ease,border-color .18s ease,box-shadow .18s ease;
}
.serviceDockItem:hover{
  transform:translateY(-2px);
  border-color:var(--good,#4ade80);
  box-shadow:0 12px 28px rgba(0,0,0,.4);
}
.serviceDockItem img{width:24px;height:24px;display:block}
.serviceDockItem.problem{border-color:color-mix(in srgb,var(--warn,#f59e0b) 60%,var(--line))}
/* Live pip. Pulses so "connected" reads as active rather than merely present. */
.serviceDockLive{
  position:absolute;top:-3px;right:-3px;
  width:12px;height:12px;border-radius:50%;
  background:var(--good,#4ade80);
  border:2px solid var(--panel);
  box-shadow:0 0 8px color-mix(in srgb,var(--good,#4ade80) 70%,transparent);
  animation:serviceDockPulse 2.4s ease-in-out infinite;
}
.serviceDockItem.problem .serviceDockLive{
  background:var(--warn,#f59e0b);
  box-shadow:0 0 8px color-mix(in srgb,var(--warn,#f59e0b) 70%,transparent);
}
@keyframes serviceDockPulse{0%,100%{opacity:1}50%{opacity:.45}}
.serviceDockPop{
  position:fixed;inset:0;z-index:11500;
  display:grid;align-items:end;justify-items:start;
  padding:16px 16px 74px 16px;
  background:transparent;
}
.serviceDockCard{
  /* Wide enough that the active-print card's 2-column stats + buttons sit INSIDE the frame.
     No overflow rule here on purpose — that caused a scrollbar before; the minmax stats grid
     below guarantees the content can never spill past this border. */
  width:min(460px,calc(100vw - 32px));
  position:relative;
  display:grid;gap:10px;padding:14px;
  border:1px solid color-mix(in srgb,var(--accent) 34%,var(--line));
  border-radius:14px;
  background:color-mix(in srgb,var(--panel) 97%,black 3%);
  box-shadow:0 22px 50px rgba(0,0,0,.5);
}
/* Grid children default to min-width:auto and grow to their content's min-content — so the long
   nowrap print title forced the green active box WIDER than the frame on narrow screens. Let every
   child shrink to the card; the title then truncates (ellipsis) and stats wrap instead of spilling. */
.serviceDockCard > *{ min-width:0; }
/* padding-right clears the × (its left edge sits 39px from the card's right edge): the
   inline "Live" pill's right edge lands at this distance, so 48px keeps a gap instead of
   the pill tucking under the ×. Also keeps the title truncating before the × on mobile. */
.serviceDockCard header{display:flex;align-items:center;gap:10px;padding-right:48px}
/* Close (×) in the top-right corner of the Bambu/OctoPrint dock popover — desktop + mobile. */
.serviceDockClose{
  position:absolute; top:11px; right:11px; z-index:3;
  width:28px; height:28px; display:grid; place-items:center;
  border:1px solid var(--line); border-radius:50%;
  background:color-mix(in srgb, var(--panel2, var(--panel)) 85%, transparent);
  color:var(--muted); font-size:1.2rem; line-height:1; cursor:pointer;
  transition:color .15s ease, border-color .15s ease, background .15s ease;
}
.serviceDockClose:hover{ color:var(--text); border-color:color-mix(in srgb, var(--accent) 55%, var(--line)); background:color-mix(in srgb, var(--accent) 12%, var(--panel2, var(--panel))); }
.serviceDockLogo{
  display:grid;place-items:center;width:34px;height:34px;flex:0 0 auto;
  border:1px solid color-mix(in srgb,var(--accent) 30%,var(--line));
  border-radius:10px;background:color-mix(in srgb,var(--accent) 8%,transparent);
}
.serviceDockLogo img{width:20px;height:20px;display:block}
.serviceDockCard header>div{flex:1 1 auto;min-width:0}
.serviceDockCard strong{display:block;font-size:.86rem}
.serviceDockCard small{display:block;color:var(--muted);font-size:.72rem;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.serviceDockState{
  padding:3px 9px;border-radius:999px;white-space:nowrap;
  border:1px solid color-mix(in srgb,var(--good,#4ade80) 50%,var(--line));
  color:var(--good,#4ade80);font-size:.6rem;font-weight:900;
  letter-spacing:.09em;text-transform:uppercase;
}
.serviceDockState.bad{
  border-color:color-mix(in srgb,var(--warn,#f59e0b) 55%,var(--line));
  color:var(--warn,#f59e0b);
}
/* Mobile: the narrow card can't hold the logo + title AND the "Live" pill AND clear the ×
   in the corner. Drop the pill out of the header flex flow and stack it directly UNDER the
   close button — both right-aligned in the top-right corner. It positions against the card
   (.serviceDockCard is position:relative; the header is static), so top/right are card-relative,
   matching the × at top:11px/right:11px. Desktop keeps it inline (it has the room). */
@media(max-width:560px){
  .serviceDockCard header .serviceDockState{
    position:absolute; top:56px; right:11px; z-index:3; margin:0;
    /* opaque (matches the card) so a wrapped "…awaiting confirmation" line can't show
       through the pill's border. */
    background:color-mix(in srgb,var(--panel) 97%,black 3%);
  }
}
.serviceDockCard p{margin:0;color:var(--muted);font-size:.75rem;line-height:1.45}
.serviceDockErr{color:var(--warn,#f59e0b)!important}
/* Backup freshness. "Up to date" stays quiet; unsaved changes are the only
   state the user needs to act on, so only that one is given colour + weight. */
.serviceDockStale{display:flex;align-items:center;gap:6px}
.serviceDockStale::before{
  content:'';width:6px;height:6px;border-radius:50%;flex:0 0 auto;
  background:var(--good,#4ade80);
}
.serviceDockStale.due{color:var(--warn,#f59e0b)!important;font-weight:700}
.serviceDockStale.due::before{background:var(--warn,#f59e0b)}
/* What changed, under how much changed. Indented under the count it explains. */
.serviceDockChanged{
  margin-left:12px!important;padding-left:10px;
  border-left:1px solid color-mix(in srgb,var(--warn,#f59e0b) 40%,transparent);
  font-size:.7rem!important;opacity:.9;
}
.serviceDockSched{opacity:.85}
/* The schedule only raises a reminder — it cannot upload on its own (Pages has
   no cron). Saying so on the card prevents the worst misreading of a green dot. */
.serviceDockManual{
  margin-top:2px!important;padding-top:8px;border-top:1px solid var(--line);
  font-size:.7rem!important;opacity:.8;
}
.serviceDockManual b{font-weight:700;opacity:.95}
.serviceDockActions{display:flex;gap:8px}
/* Dock card actions (View reports / Recent orders / Disconnect): lighter and a
   touch smaller than the app's default heavy button, so they read as compact
   dock controls rather than big bold CTAs. */
.serviceDockActions .btn{flex:1 1 0;min-height:32px;padding:0 9px;font-size:.72rem;font-weight:600;letter-spacing:.01em}
@media(max-width:620px){
  .serviceDock{left:12px;bottom:12px;gap:8px}
  .serviceDockItem{width:42px;height:42px}
  .serviceDockItem img{width:21px;height:21px}
}
@media(prefers-reduced-motion:reduce){
  .serviceDockLive{animation:none}
  .serviceDockItem{transition:none}
}
.serviceDockDiag{font-size:.7rem!important;opacity:.85;padding-top:2px;border-top:1px solid color-mix(in srgb,var(--line) 70%,transparent)}
.serviceDockDiag b{color:var(--text)}
.serviceDockTest{
  width:100%;min-height:32px;padding:0 10px;cursor:pointer;
  border:1px dashed color-mix(in srgb,var(--accent) 45%,var(--line));
  border-radius:9px;background:transparent;
  color:var(--muted);font-size:.73rem;font-weight:700;
}
.serviceDockTest:hover{color:var(--text);border-color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,transparent)}
/* The Complete-Print action on the "already dealt with / off-cloud" message is
   the recommended fix, so it reads as a solid CTA, not the dashed secondary. */
.bambuIdleComplete{
  margin-top:10px;border-style:solid;color:var(--accent2,var(--accent));
  border-color:color-mix(in srgb,var(--accent) 55%,var(--line));
  background:color-mix(in srgb,var(--accent) 10%,transparent);
}
.bambuIdleComplete:hover{color:var(--accent-ink,#fff);background:var(--accent);border-color:var(--accent)}
/* Faint context line on the calm idle state (printer idle, only old records). */
.bambuIdleAged{opacity:.7;font-size:.92em}
/* Pre-extrusion phase: reads as information, not as a filament measurement. */
.bambuCalibrating{color:var(--muted)!important;font-style:italic}
/* ---- dock: waiting-jobs badge ---- */
.serviceDockCount{
  position:absolute;bottom:-4px;right:-4px;
  min-width:19px;height:19px;padding:0 5px;
  display:grid;place-items:center;border-radius:999px;
  background:var(--accent);color:var(--accent-ink,#fff);
  border:2px solid var(--panel);
  font-size:.63rem;font-weight:900;line-height:1;
}
/* A job waiting is worth noticing, so the tile itself breathes. */
.serviceDockItem.waiting{
  border-color:var(--accent);
  animation:serviceDockWaiting 2.2s ease-in-out infinite;
}
@keyframes serviceDockWaiting{
  0%,100%{box-shadow:0 8px 22px rgba(0,0,0,.35),0 0 0 0 color-mix(in srgb,var(--accent) 55%,transparent)}
  50%{box-shadow:0 8px 22px rgba(0,0,0,.35),0 0 0 9px color-mix(in srgb,var(--accent) 0%,transparent)}
}
/* A print in progress. Its own green badge (animated "activity" bars) + breathing
   glow, kept deliberately distinct from the accent "awaiting confirmation" count so
   "printing now" and "confirm me" never look the same. */
.serviceDockPrinting{
  position:absolute;bottom:-4px;right:-4px;
  min-width:19px;height:19px;padding:0 5px;
  display:flex;align-items:center;justify-content:center;gap:2px;
  border-radius:999px;
  background:var(--good,#4ade80);
  border:2px solid var(--panel);
  box-shadow:0 0 8px color-mix(in srgb,var(--good,#4ade80) 60%,transparent);
}
.serviceDockPrinting i{
  width:2px;height:7px;border-radius:1px;
  background:var(--panel);
  animation:serviceDockBars .9s ease-in-out infinite;
}
.serviceDockPrinting i:nth-child(2){animation-delay:.15s}
.serviceDockPrinting i:nth-child(3){animation-delay:.3s}
@keyframes serviceDockBars{0%,100%{transform:scaleY(.5)}50%{transform:scaleY(1.3)}}
/* When a finished job is ALSO waiting, the accent count owns the bottom-right slot,
   so the printing badge steps aside to the bottom-left — both stay readable. */
.serviceDockItem.waiting .serviceDockPrinting{right:auto;left:-4px}
/* Breathing glow so a just-started print is noticed before you even hunt for the badge. */
.serviceDockItem.printing{
  border-color:var(--good,#4ade80);
  animation:serviceDockPrinting 2.2s ease-in-out infinite;
}
/* Printing AND waiting → the accent waiting glow wins; a job needing action is louder. */
.serviceDockItem.waiting.printing{animation:serviceDockWaiting 2.2s ease-in-out infinite}
@keyframes serviceDockPrinting{
  0%,100%{box-shadow:0 8px 22px rgba(0,0,0,.35),0 0 0 0 color-mix(in srgb,var(--good,#4ade80) 55%,transparent)}
  50%{box-shadow:0 8px 22px rgba(0,0,0,.35),0 0 0 9px color-mix(in srgb,var(--good,#4ade80) 0%,transparent)}
}
/* ---- confirm dialog: clearer mapping row ---- */
.bambuMap{display:grid;gap:3px;min-width:0}
.bambuMap>span{color:var(--muted);font-size:.6rem;font-weight:800;letter-spacing:.06em;text-transform:uppercase}
/* ---- deduction animation ---- */
.deductStage{
  position:fixed;inset:0;z-index:2147483500;
  display:grid;place-items:center;pointer-events:none;
  opacity:0;transition:opacity .28s ease;
}
.deductStage.in{opacity:1}
.deductCard{
  display:grid;justify-items:center;gap:5px;
  padding:24px 30px;
  border:1px solid color-mix(in srgb,var(--good,#4ade80) 55%,var(--line));
  border-radius:20px;
  background:color-mix(in srgb,var(--panel) 96%,black 4%);
  box-shadow:0 26px 70px rgba(0,0,0,.55),0 0 40px color-mix(in srgb,var(--good,#4ade80) 18%,transparent);
  animation:deductPop .45s cubic-bezier(.2,.85,.2,1) both;
}
@keyframes deductPop{from{opacity:0;transform:translateY(16px) scale(.9)}to{opacity:1;transform:none}}
.deductRing{
  position:relative;display:grid;place-items:center;
  width:48px;height:48px;border-radius:50%;
  border:2px solid color-mix(in srgb,var(--good,#4ade80) 45%,var(--line));
  margin-bottom:3px;
}
.deductRing i{
  position:absolute;inset:-2px;border-radius:50%;
  border:2px solid transparent;border-top-color:var(--good,#4ade80);
  animation:deductSpin .9s linear infinite;
}
.deductRing::after{
  content:"";width:16px;height:9px;
  border:solid var(--good,#4ade80);border-width:0 0 2.5px 2.5px;
  transform:rotate(-45deg) translateY(-2px);
}
@keyframes deductSpin{to{transform:rotate(360deg)}}
.deductAmount{
  font-size:2rem;letter-spacing:-.03em;color:var(--good,#4ade80);
  font-variant-numeric:tabular-nums;line-height:1;
}
.deductLabel{color:var(--text);font-size:.8rem;font-weight:700}
.deductDetail{color:var(--muted);font-size:.72rem;text-align:center;max-width:34ch}
/* the tab it lands on acknowledges the hit */
.deductLanded{
  animation:deductLand .85s ease-out;
}
@keyframes deductLand{
  0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--good,#4ade80) 60%,transparent)}
  60%{box-shadow:0 0 0 12px color-mix(in srgb,var(--good,#4ade80) 0%,transparent)}
  100%{box-shadow:none}
}
@media(prefers-reduced-motion:reduce){
  .deductStage,.deductCard,.deductRing i,.serviceDockItem.waiting,
  .serviceDockItem.printing,.serviceDockPrinting i{animation:none!important;transition:none!important}
}
/* ---- print history browser ---- */
.bambuWide{width:min(640px,100%)}
.bambuJobs{display:grid;gap:8px}
.bambuLoading{margin:0;color:var(--muted);font-size:.8rem;text-align:center;padding:18px 0}
.bambuJob{
  display:grid;
  grid-template-columns:1fr auto;
  grid-template-areas:"main state" "actions actions";
  gap:8px 12px;align-items:center;
  padding:11px 12px;
  border:1px solid color-mix(in srgb,var(--line) 80%,transparent);
  border-radius:11px;
  background:color-mix(in srgb,var(--surface-input,var(--panel)) 84%,transparent);
}
.bambuJob.pending{border-color:color-mix(in srgb,var(--accent) 50%,var(--line));background:color-mix(in srgb,var(--accent) 7%,transparent)}
.bambuJob.done{opacity:.72}
.bambuJob.dim{opacity:.55}
.bambuJobMain{grid-area:main;min-width:0}
.bambuJobMain strong{display:block;font-size:.83rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bambuJobMain small{display:block;color:var(--muted);font-size:.71rem;margin-top:2px}
.bambuJobState{
  grid-area:state;white-space:nowrap;
  padding:3px 9px;border-radius:999px;
  border:1px solid color-mix(in srgb,var(--line) 85%,transparent);
  color:var(--muted);font-size:.6rem;font-weight:800;
  letter-spacing:.07em;text-transform:uppercase;
}
.bambuJobState.pending{border-color:color-mix(in srgb,var(--accent) 55%,var(--line));color:var(--accent2,var(--accent))}
.bambuJobState.done{border-color:color-mix(in srgb,var(--good,#4ade80) 50%,var(--line));color:var(--good,#4ade80)}
.bambuJobActions{grid-area:actions;display:flex;gap:7px;flex-wrap:wrap;justify-content:flex-end}
.bambuJobActions .btn{min-height:32px;padding:0 12px;font-size:.75rem}
@media(max-width:560px){
  .bambuJob{grid-template-columns:1fr;grid-template-areas:"main" "state" "actions"}
  .bambuJobState{justify-self:start}
  .bambuJobActions{justify-content:stretch}
  .bambuJobActions .btn{flex:1 1 auto}
}
/* ---- "check now" scan feedback ---- */
.serviceDockScan{
  display:flex;align-items:center;gap:8px;
  margin:0!important;padding:8px 10px;
  border:1px solid color-mix(in srgb,var(--accent) 28%,var(--line));
  border-radius:9px;
  background:color-mix(in srgb,var(--accent) 8%,transparent);
  color:var(--text);font-size:.74rem;line-height:1.35;
  animation:scanIn .26s ease both;
}
.serviceDockScan[hidden]{display:none}
@keyframes scanIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.serviceDockScan.done{
  border-color:color-mix(in srgb,var(--good,#4ade80) 50%,var(--line));
  background:color-mix(in srgb,var(--good,#4ade80) 10%,transparent);
}
.serviceDockScan.none{
  border-color:color-mix(in srgb,var(--line) 85%,transparent);
  background:color-mix(in srgb,var(--surface-input,var(--panel)) 80%,transparent);
  color:var(--muted);
}
/* three dots that chase while a stage is running */
.scanDots{display:inline-flex;gap:3px;flex:0 0 auto}
.scanDots i{
  width:5px;height:5px;border-radius:50%;
  background:var(--accent2,var(--accent));
  animation:scanDot 1.05s ease-in-out infinite;
}
.scanDots i:nth-child(2){animation-delay:.16s}
.scanDots i:nth-child(3){animation-delay:.32s}
@keyframes scanDot{0%,100%{opacity:.25;transform:translateY(0)}50%{opacity:1;transform:translateY(-3px)}}
/* radar sweep over the dock tile while it is checking */
.serviceDockItem.scanning{border-color:var(--accent2,var(--accent))}
.serviceDockItem.scanning::after{
  content:"";
  position:absolute;inset:-1px;
  border-radius:inherit;
  background:conic-gradient(from 0deg,transparent 0deg,color-mix(in srgb,var(--accent2,var(--accent)) 55%,transparent) 60deg,transparent 120deg);
  animation:scanSweep 1s linear infinite;
  pointer-events:none;
}
@keyframes scanSweep{to{transform:rotate(360deg)}}
@media(prefers-reduced-motion:reduce){
  .serviceDockScan,.scanDots i,.serviceDockItem.scanning::after{animation:none!important}
  .serviceDockItem.scanning::after{display:none}
}
/* ---- job thumbnail + warning states ---- */
.bambuJob{grid-template-columns:auto 1fr auto;grid-template-areas:"thumb main state" "thumb actions actions"}
.bambuJobThumb{
  grid-area:thumb;display:grid;place-items:center;
  width:46px;height:46px;flex:0 0 auto;overflow:hidden;
  border:1px solid color-mix(in srgb,var(--line) 80%,transparent);
  border-radius:9px;background:color-mix(in srgb,var(--surface-raised,var(--panel2)) 90%,transparent);
}
.bambuJobThumb img{width:100%;height:100%;object-fit:cover;display:block}
/* Bambu covers are often a flat grey plate render, so an empty one still needs
   to look deliberate rather than broken. */
.bambuJobThumb.empty::after{
  content:"";width:18px;height:18px;border-radius:4px;
  border:2px solid color-mix(in srgb,var(--muted) 45%,transparent);
}
.bambuError.warn{
  border-color:color-mix(in srgb,var(--warn,#f59e0b) 55%,var(--line))!important;
  background:color-mix(in srgb,var(--warn,#f59e0b) 12%,transparent)!important;
}
@media(max-width:560px){
  .bambuJob{grid-template-columns:auto 1fr;grid-template-areas:"thumb main" "state state" "actions actions"}
}
/* per-row match confidence tag */
.bambuMatchTag{
  margin-left:7px;padding:1px 7px;border-radius:999px;font-style:normal;
  font-size:.58rem;font-weight:800;letter-spacing:.05em;text-transform:uppercase;
  border:1px solid color-mix(in srgb,var(--warn,#f59e0b) 45%,var(--line));
  color:var(--warn,#f59e0b);
}
.bambuMatchTag.good{
  border-color:color-mix(in srgb,var(--good,#4ade80) 50%,var(--line));
  color:var(--good,#4ade80);
}
.bambuChip{flex-wrap:wrap}
/* ---- live "active print" readout in the dock popover ---- */
.bambuActive{ margin:10px 0 2px; padding:11px 13px; border:1px solid color-mix(in srgb, var(--good,#16a34a) 38%, var(--line)); border-radius:12px; background:color-mix(in srgb, var(--good,#16a34a) 9%, transparent); }
.bambuActiveTop{ display:flex; align-items:center; gap:8px; }
.bambuActiveTop strong{ font-size:.82rem; }
.bambuActiveDot{ width:9px; height:9px; border-radius:50%; background:var(--good,#16a34a); box-shadow:0 0 0 0 color-mix(in srgb, var(--good,#16a34a) 60%, transparent); animation:bambuPulse 1.9s ease-out infinite; }
@keyframes bambuPulse{ to{ box-shadow:0 0 0 9px transparent; } }
.bambuActiveTitle{ margin:5px 0 8px; color:var(--muted); font-size:.78rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.bambuActiveBar{ height:7px; border-radius:999px; overflow:hidden; background:color-mix(in srgb, var(--line) 65%, transparent); }
.bambuActiveBar i{ display:block; height:100%; width:var(--p,0%); border-radius:999px; background:linear-gradient(90deg, color-mix(in srgb, var(--good,#16a34a) 70%, var(--accent)), var(--good,#16a34a)); transition:width .6s ease; }
.bambuActiveMeta{ margin:8px 0 0; font-size:.76rem; color:var(--muted); }
/* Estimated consumption. Given a little more weight than the meta line since it
   is the number a user is actually watching, but still clearly secondary to the
   confirmed amount they will deduct later. */
.bambuActiveUsed{
  display:flex; align-items:center; gap:7px; flex-wrap:wrap;
  margin:5px 0 0; font-size:.76rem; color:var(--muted);
}
.bambuActiveUsed b{ color:var(--accent2, var(--accent)); font-weight:800; font-variant-numeric:tabular-nums; }
.bambuActiveUsed span{ opacity:.75; }
.bambuActiveMeta b{ color:var(--text); }
.bambuActiveNote{ margin:5px 0 0; font-size:.71rem; color:var(--muted); opacity:.85; }
/* ---- stale "still printing" job ----
   Deliberately NOT green. The green card means "this is happening now"; this
   card means the app has lost track of the print, so it takes the warning
   colour and drops the pulsing dot -- a pulse reads as live activity, which is
   the one thing that is not true here. */
.bambuActive.bambuStale{
  border-color:color-mix(in srgb, var(--warn,#f59e0b) 45%, var(--line));
  background:color-mix(in srgb, var(--warn,#f59e0b) 9%, transparent);
}
.bambuStaleDot{
  width:9px; height:9px; border-radius:50%;
  background:var(--warn,#f59e0b);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--warn,#f59e0b) 22%, transparent);
}
.bambuStaleWhy{ margin:9px 0 0; font-size:.74rem; line-height:1.5; color:var(--muted); }
.bambuStaleWhy b{ color:var(--text); white-space:nowrap; }
.bambuStaleActions{ display:flex; gap:8px; flex-wrap:wrap; margin:11px 0 0; }
.bambuStaleActions button{
  appearance:none; cursor:pointer; flex:1 1 auto; min-width:118px;
  padding:8px 12px; border-radius:9px;
  font:700 .76rem var(--app-font,system-ui);
  border:1px solid var(--line); background:var(--surface-input,var(--panel)); color:var(--text);
  transition:border-color .15s ease, background .15s ease, transform .12s ease;
}
.bambuStaleActions button:hover{ border-color:var(--accent); transform:translateY(-1px); }
.bambuStaleActions button:disabled{ opacity:.6; cursor:default; transform:none; }
.bambuStaleDone{
  border-color:color-mix(in srgb, var(--good,#16a34a) 50%, var(--line))!important;
  color:var(--good,#16a34a)!important;
}
.bambuStaleNote{ margin:9px 0 0; font-size:.71rem; line-height:1.5; color:var(--muted); opacity:.85; }
.bambuStaleNote b{ color:var(--text); }

/* ---- failed-print estimate dialog ---- */
.bambuFailChips{ display:flex; flex-wrap:wrap; gap:8px; margin:14px 0 12px; }
.bambuFailChip{ appearance:none; cursor:pointer; padding:8px 13px; border-radius:999px; font:650 .78rem var(--app-font,system-ui); border:1px solid var(--line); background:var(--surface-input,var(--panel)); color:var(--text); transition:border-color .15s ease, background .15s ease; }
.bambuFailChip:hover{ border-color:var(--accent); }
.bambuFailChip.on{ border-color:var(--accent); background:color-mix(in srgb, var(--accent) 20%, transparent); color:var(--accent2,var(--accent)); }
.bambuFailRange{ display:block; margin:4px 0 10px; }
.bambuFailRange input[type=range]{ width:100%; accent-color:var(--accent); cursor:pointer; }
.bambuFailReadout{ margin:0; font-size:.86rem; color:var(--muted); }
.bambuFailReadout b{ color:var(--text); font-variant-numeric:tabular-nums; }
.bambuNoActive{ margin:10px 0 2px; padding:9px 12px; border:1px dashed var(--line); border-radius:11px; color:var(--muted); font-size:.78rem; }
/* Indeterminate progress: shown when we have no trustworthy end estimate, so the
   bar reads as "running" instead of claiming a percentage we do not know. */
.bambuActiveBar.indeterminate{ position:relative; overflow:hidden; }
.bambuActiveBar.indeterminate i{
  background:linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--good,#16a34a) 85%, transparent) 45%,
    color-mix(in srgb, var(--good,#16a34a) 85%, transparent) 55%,
    transparent 100%);
  background-size:45% 100%;
  background-repeat:no-repeat;
  animation:bambuIndet 1.5s ease-in-out infinite;
}
@keyframes bambuIndet{ 0%{background-position:-45% 0} 100%{background-position:145% 0} }
@media(prefers-reduced-motion:reduce){ .bambuActiveBar.indeterminate i{ animation:none; background-position:50% 0; } }
.bambuNoActive.warming{
  border-style:solid;
  border-color:color-mix(in srgb,var(--warn,#d97706) 34%,var(--line));
  background:color-mix(in srgb,var(--warn,#d97706) 8%,transparent);
  color:var(--muted);
}
.bambuNoActive.warming b{ color:var(--text); }
/* The job currently on the printer, listed alongside recent prints. */
.bambuJobState.live{
  color:var(--good,#16a34a);
  border-color:color-mix(in srgb,var(--good,#16a34a) 55%,var(--line));
  background:color-mix(in srgb,var(--good,#16a34a) 14%,transparent);
}
.bambuJob.live{ border-color:color-mix(in srgb,var(--good,#16a34a) 42%,var(--line)); }
/* Detected failure point: a recommendation, not a lock — the slider and quick
   choices below stay fully in control. */
.bambuFailDetected{
  margin:0 0 4px; padding:11px 13px; border-radius:11px;
  border:1px solid color-mix(in srgb,var(--warn,#d97706) 42%,var(--line));
  background:color-mix(in srgb,var(--warn,#d97706) 11%,transparent);
  color:var(--muted); font-size:.86rem; line-height:1.5;
}
.bambuFailDetected b{ color:var(--text); }
.bambuJobState.bad{
  color:var(--bad,#dc2626);
  border-color:color-mix(in srgb,var(--bad,#dc2626) 55%,var(--line));
  background:color-mix(in srgb,var(--bad,#dc2626) 12%,transparent);
}
/* ---- live progress dialog ----
   Opened from "See progress" on the dock card. Shows the telemetry we actually
   have at a size worth opening; there is no camera feed, and the dialog says so
   rather than leaving an empty frame where video would be. */
.bambuProgressBackdrop{
  position:fixed; inset:0; z-index:11000; display:grid; place-items:center; padding:20px;
  background:rgba(3,6,10,.72); backdrop-filter:blur(8px);
  opacity:0; transition:opacity .22s ease;
}
.bambuProgressBackdrop.in{ opacity:1; }
.bambuProgressDialog{
  width:min(520px,100%); max-height:min(90vh,760px); overflow:auto;
  border:1px solid color-mix(in srgb, var(--accent) 55%, var(--line));
  border-radius:20px; background:var(--surface-main, var(--panel)); color:var(--text);
  box-shadow:0 28px 80px rgba(0,0,0,.5);
  transform:translateY(10px) scale(.985); transition:transform .28s cubic-bezier(.2,.8,.2,1);
}
.bambuProgressBackdrop.in .bambuProgressDialog{ transform:none; }
.bambuProgressDialog > header{
  display:flex; align-items:flex-start; gap:13px; padding:18px 18px 15px;
  border-bottom:1px solid var(--line);
  background:linear-gradient(135deg, color-mix(in srgb, var(--accent) 12%, transparent), transparent 60%);
}
.bambuProgressDialog > header > div{ flex:1 1 auto; min-width:0; }
.bambuProgressDialog > header span:first-child{ flex:0 0 auto; }
.bambuProgressDialog > header > div > span{
  display:block; color:var(--accent2, var(--accent));
  font-size:.6rem; font-weight:900; letter-spacing:.13em;
}
.bambuProgressDialog h2{ margin:3px 0 0; font-size:1.08rem; line-height:1.3; overflow-wrap:anywhere; }
.bambuProgressDialog h2 + small{ display:block; margin-top:3px; color:var(--muted); font-size:.76rem; }
.bambuProgressBody{ padding:20px 18px 6px; }
/* Progress ring */
.bambuRingWrap{ position:relative; width:170px; margin:0 auto 18px; }
/* overflow:visible is load-bearing — the fill's glow extends past the circle, and an
   SVG root clips to its viewBox by default, which sliced the halo into a hard flat
   edge at 12/3/6/9 o'clock. Letting it paint outside the viewBox restores a clean,
   round falloff. */
.bambuRing{ width:170px; height:170px; transform:rotate(-90deg); display:block; overflow:visible; }
.bambuRingTrack{
  fill:none; stroke-width:9; stroke-linecap:round;
  stroke:color-mix(in srgb, var(--line) 78%, transparent);
}
.bambuRingFill{
  fill:none; stroke:var(--accent); stroke-width:9; stroke-linecap:round;
  transition:stroke-dashoffset .6s cubic-bezier(.2,.8,.2,1);
  /* Layered halo: a tight, bright core + a soft wide bloom = a smooth, professional
     falloff instead of a single hard-radius ring of light. */
  filter:
    drop-shadow(0 0 1.5px color-mix(in srgb, var(--accent) 60%, transparent))
    drop-shadow(0 0 6px color-mix(in srgb, var(--accent) 34%, transparent))
    drop-shadow(0 0 12px color-mix(in srgb, var(--accent) 18%, transparent));
}
/* No trustworthy percentage: sweep instead of implying a figure. */
.bambuRing.unknown .bambuRingFill{ opacity:.55; animation:bambuRingSweep 1.9s linear infinite; }
@keyframes bambuRingSweep{ to{ transform:rotate(360deg); } }
.bambuRing.unknown{ transform:rotate(-90deg); }
.bambuRingCentre{
  position:absolute; inset:0; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:3px; pointer-events:none;
}
.bambuRingCentre strong{ font-size:1.85rem; font-weight:800; font-variant-numeric:tabular-nums; line-height:1; }
.bambuRingCentre small{ color:var(--accent); font-weight:700; font-size:.74rem; text-align:center; padding:0 22px; }
/* Stats */
.bambuProgressStats{ display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:9px; margin:0 0 15px; }
.bambuProgressStats > div{
  padding:10px 12px; border:1px solid var(--line); border-radius:12px;
  background:var(--surface-raised, var(--panel2, var(--panel)));
}
/* Information titles carry the theme accent. */
.bambuProgressStats dt{ color:var(--accent); font-size:.68rem; font-weight:800; letter-spacing:.05em; text-transform:uppercase; }
.bambuProgressStats dd{ margin:4px 0 0; font-size:.9rem; font-weight:700; font-variant-numeric:tabular-nums; }
.bambuProgressNote{
  margin:0 0 4px; padding:12px 13px; border-radius:12px;
  border:1px solid var(--line); background:var(--surface-sub, var(--panel2, var(--panel)));
  color:var(--muted); font-size:.76rem; line-height:1.6;
}
.bambuProgressNote b{ color:var(--text); }
.bambuProgressFoot{ display:flex; gap:9px; padding:14px 18px 18px; }
.bambuProgressFoot .btn{ flex:1 1 0; min-height:42px; }
@media(max-width:560px){ .bambuProgressStats{ grid-template-columns:1fr; } }

/* ---- Dock CARD active print: the ring + stats now live inline on the card,
   like the retired "See progress" window (its camera was dropped). Sized to fit
   the popover. ---- */
.serviceDockCard .bambuActiveFancy .bambuActiveTitle{ margin:2px 0 0; }
.serviceDockCard .bambuCardRing{ width:154px; margin:14px auto 16px; }
.serviceDockCard .bambuCardRing .bambuRing{ width:154px; height:154px; }
.serviceDockCard .bambuCardRing .bambuRingTrack,
.serviceDockCard .bambuCardRing .bambuRingFill{ stroke-width:8; }
.serviceDockCard .bambuCardRing .bambuRingCentre strong{ font-size:1.55rem; }
.serviceDockCard .bambuCardRing .bambuRingCentre small{ font-size:.64rem; padding:0 12px; }
.serviceDockCard .bambuCardStats{ grid-template-columns:minmax(0,1fr) minmax(0,1fr); gap:7px; margin:2px 0 12px; }
.serviceDockCard .bambuCardStats > div{ padding:8px 10px; border-radius:11px; }
.serviceDockCard .bambuCardStats dt{ font-size:.58rem; }
.serviceDockCard .bambuCardStats dd{ font-size:.82rem; margin-top:3px; }
/* On phones, stack the dock-card stats to one column — this selector is specific enough to beat
   the generic @media rule above, which is why the stats stayed 2-wide and ran off-screen. */
@media(max-width:560px){ .serviceDockCard .bambuCardStats{ grid-template-columns:1fr; } }
@media(prefers-reduced-motion:reduce){
  .bambuProgressBackdrop, .bambuProgressDialog, .bambuRingFill{ transition:none; }
  .bambuRing.unknown .bambuRingFill{ animation:none; }
}

/* ---- drag to reorder the dock ----
   The tiles are moved by JS: the dragged one is fixed-position and follows the
   pointer; the others glide via FLIP, which sets its own transform transition
   inline. So the slots carry NO base transform transition of their own -- one
   here would fight the FLIP timing. Only opacity animates by default. */
.serviceDock .serviceDockItem{ touch-action:none; }
.serviceDockSlot{ transition:opacity .16s ease; }
.serviceDockSlot.isDragging{
  opacity:.95; cursor:grabbing;
  transition:none;                 /* pinned to the pointer every frame from JS */
  filter:drop-shadow(0 12px 22px rgba(0,0,0,.55));
}
.serviceDockSlot.isDragging .serviceDockItem{ border-color:var(--accent); }
/* The gap the dragged tile leaves behind: a dashed well, so the drop target is
   obvious without a solid block jumping around. */
.dockPlaceholder{
  border:1.5px dashed color-mix(in srgb, var(--accent) 55%, var(--line));
  border-radius:12px;
  background:color-mix(in srgb, var(--accent) 8%, transparent);
}
.dockPlaceholder *{ display:none; }
/* While reordering, tiles must not look clickable and the page must not select. */
.serviceDock.isReordering{ user-select:none; }
.serviceDock.isReordering .serviceDockItem{ cursor:grabbing; }
@media (prefers-reduced-motion: reduce){
  .serviceDockSlot{ transition:none; }
}

/* Belt-and-braces with the dragstart handler in dock-reorder: images are
   draggable by default, and a native image drag shows the no-drop cursor and
   eats the reorder gesture. */
.serviceDock .serviceDockItem, .serviceDock .serviceDockItem img{
  -webkit-user-drag:none; user-drag:none; user-select:none;
}

/* Finished-print card: big completion checkmark + direct message (2026-07-24) */
.bambuFinished{display:flex;flex-direction:column;align-items:center;text-align:center;gap:12px;padding:8px 0 2px}
.bambuDoneRing{position:relative;width:118px;height:118px;flex:0 0 118px}
.bambuDoneRing > svg{width:100%;height:100%;transform:rotate(-90deg);overflow:visible}
.bambuDoneTrack{fill:none;stroke:color-mix(in srgb,var(--good,#3fb950) 22%,transparent);stroke-width:8}
.bambuDoneFill{fill:none;stroke:var(--good,#3fb950);stroke-width:8;stroke-linecap:round;stroke-dasharray:326.7;stroke-dashoffset:326.7;animation:bambuDoneSweep .9s cubic-bezier(.2,.7,.2,1) forwards;filter:drop-shadow(0 0 1.5px color-mix(in srgb,var(--good,#3fb950) 55%,transparent)) drop-shadow(0 0 8px color-mix(in srgb,var(--good,#3fb950) 26%,transparent))}
@keyframes bambuDoneSweep{to{stroke-dashoffset:0}}
/* Check sits in the upper half; the "100% / COMPLETE" caption rides the lower half where
   the circle is wide enough to hold it, so neither crosses the ring edge. */
.bambuDoneCheck{position:absolute;inset:0 0 38px;display:grid;place-items:center;pointer-events:none}
.bambuDoneCheck svg{width:46px;height:46px;fill:none;stroke:var(--good,#3fb950);stroke-width:3;stroke-linecap:round;stroke-linejoin:round;filter:drop-shadow(0 2px 12px color-mix(in srgb,var(--good,#3fb950) 60%,transparent));animation:bambuCheckPop .45s .5s both}
@keyframes bambuCheckPop{from{opacity:0;transform:scale(.5)}to{opacity:1;transform:scale(1)}}
.bambuDonePct{position:absolute;left:0;right:0;bottom:19px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;line-height:1;color:var(--good,#3fb950);pointer-events:none}
.bambuDonePct b{font-size:.92rem;font-weight:800;font-variant-numeric:tabular-nums}
.bambuDonePct span{font-size:.5rem;font-weight:800;letter-spacing:.11em;text-transform:uppercase;opacity:.92}
/* First-expand celebration: 12 specks fly outward once the ring finishes its 0->100 sweep. */
.bambuBurst{position:absolute;inset:0;pointer-events:none;overflow:visible}
.bambuBurst i{position:absolute;top:50%;left:50%;width:7px;height:7px;margin:-3.5px 0 0 -3.5px;border-radius:2px;background:var(--good,#3fb950);opacity:0;transform:rotate(var(--a)) translateY(-6px) scale(.3);animation:bambuBurstFly .75s .9s cubic-bezier(.2,.7,.2,1) forwards}
.bambuBurst i:nth-child(2n){width:5px;height:5px;border-radius:50%;background:var(--accent)}
.bambuBurst i:nth-child(3n){background:var(--accent2,var(--accent))}
@keyframes bambuBurstFly{0%{opacity:0;transform:rotate(var(--a)) translateY(-6px) scale(.3)}30%{opacity:1}100%{opacity:0;transform:rotate(var(--a)) translateY(-72px) scale(1)}}
/* Reopened / refreshed: skip the sweep, check-pop and burst — just show the settled state. */
.bambuFinished.settled .bambuDoneFill{animation:none;stroke-dashoffset:0}
.bambuFinished.settled .bambuDoneCheck svg{animation:none;opacity:1;transform:none}
.bambuFinished.settled .bambuBurst{display:none}
.bambuFinishedMsg{margin:0;font-size:.92rem;line-height:1.5;color:var(--text)}
.bambuConfirmPrimary{border-color:color-mix(in srgb,var(--good,#3fb950) 55%,var(--line))!important;color:var(--good,#3fb950)!important;font-weight:800!important}
/* "Print Again" hand-off: Bambu has no reprint link we can call, so this opens Bambu Handy. */
.bambuPrintAgain{display:inline-flex;align-items:center;justify-content:center;gap:7px;text-decoration:none;white-space:nowrap}
.bambuPrintAgain svg{width:16px;height:16px;flex:0 0 16px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.bambuFinished .bambuPrintAgain{margin-top:2px;padding:9px 15px;border-radius:10px;border:1px solid color-mix(in srgb,var(--accent) 45%,var(--line));background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent2,var(--accent));font-weight:750;font-size:.9rem}
.bambuFinished .bambuPrintAgain:hover{background:color-mix(in srgb,var(--accent) 17%,transparent);border-color:var(--accent)}
.bambuPrintAgainNote{color:var(--muted);font-size:.68rem;line-height:1.35;max-width:260px}
/* Idle-state recovery link: log a print that never surfaced (LAN/Developer/re-print). */
.bambuIdleLog{display:inline-flex;align-items:center;gap:5px;margin-top:9px;padding:7px 11px;border:1px solid color-mix(in srgb,var(--accent) 40%,var(--line));border-radius:9px;background:color-mix(in srgb,var(--accent) 8%,transparent);color:var(--accent2,var(--accent));font:650 .76rem var(--app-font,system-ui);cursor:pointer}
.bambuIdleLog:hover{background:color-mix(in srgb,var(--accent) 15%,transparent);border-color:var(--accent)}
@media(prefers-reduced-motion:reduce){.bambuDoneFill{animation:none;stroke-dashoffset:0}.bambuDoneCheck svg{animation:none;opacity:1;transform:none}.bambuBurst{display:none}}
