/* ============================================================================
   PrintDrop UI — the interaction layer for /printdrop, /printdrop/owner,
   /printdrop/job and /printdrop/setup.

   WHY A SEPARATE FILE. site.css is 111 KB and dresses forty-odd pages. These
   four are the only ones anybody OPERATES rather than reads: a drop form, a
   console, a job page, a wizard. They need bigger targets, real focus rings,
   and states that read at a glance — and none of that should be able to move a
   pixel on /thesis. Loaded after site.css, so it supersedes by cascade rather
   than by !important.

   It restyles the class names the JS already emits (pd-fil-card, pd-jobcard,
   pd-send-*, pd-grip, pd-chip…) so the console modernises without touching a
   line of printdrop-owner.js. Every id in those files is a contract; nothing
   here renames anything.

   THE EDITORIAL RULE THIS ENCODES: the pages used to explain themselves in
   paragraphs beside every control. The reasoning is worth keeping — this is a
   service that asks people to trust it with a machine in their house — but it
   does not belong permanently in front of somebody who just wants to send a
   part. So it moves into .pd-why, a disclosure that is closed by default and
   one tap from open. Nothing was deleted for tidiness; it was demoted.
   ========================================================================= */

/* --------------------------------------------------------------- foundations */
.pd-drop,
.pd-machine,
.pd-group,
.pd-queue,
.pd-fil-card,
.pd-jobcard,
.pd-slot {
  /* One surface recipe, so twelve panels do not each invent a border. */
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
}

/* Numbers that change every few seconds must not reflow the line they sit in.
   Tabular figures are the whole reason a live percentage does not jitter. */
.pd-machine,
.pd-queue,
.pd-group,
.pd-metric-num,
.pd-fil-card,
.pd-jobcard,
.pd-slot {
  font-variant-numeric: tabular-nums;
}

/* A visible focus ring everywhere, and only for keyboards. The old rules
   removed the outline on inputs and put nothing back, which is a page you
   cannot see your way around with a Tab key. */
.pd-drop:focus-visible,
.pd-slot:focus-visible,
.pd-why > summary:focus-visible,
.pd-grip:focus-visible,
.pd-field input:focus-visible,
.pd-field select:focus-visible,
.pd-field textarea:focus-visible,
.pd-fil-field input:focus-visible,
.pd-btn-tap:focus-visible,
.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ------------------------------------------------------------- disclosure */
/* The honest detail, folded away. Reads as a quiet control, not as a warning. */
.pd-why {
  margin: var(--space-3) 0 0;
  border-top: 1px solid var(--border-subtle);
}
.pd-why > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) 0;
  font-family: var(--font-mono);
  font-size: var(--label-size);
  letter-spacing: var(--label-tracking);
  text-transform: uppercase;
  color: var(--text-muted);
  transition: color var(--duration-fast) var(--ease-out);
  min-height: 40px;
}
.pd-why > summary::-webkit-details-marker { display: none; }
.pd-why > summary::before {
  content: '';
  width: 6px; height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--duration-base) var(--ease-out);
}
.pd-why[open] > summary::before { transform: rotate(45deg); }
.pd-why > summary:hover { color: var(--text-body); }
.pd-why > :not(summary) {
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--text-muted);
  max-width: var(--measure-note);
  margin: 0 0 var(--space-3);
}
.pd-why strong { color: var(--text-body); font-weight: var(--weight-semibold); }

/* A one-line hint is allowed to stay visible. A paragraph is not. */
.pd-hint {
  display: block;
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  max-width: var(--measure-note);
}

/* ------------------------------------------------------------------- steps */
/* The drop flow is four decisions. Numbering them removes the need to say so. */
.pd-steps { display: grid; gap: var(--space-6); }
.pd-step { display: grid; gap: var(--space-3); }
.pd-step-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.pd-step-num {
  flex: none;
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  border: 1px solid rgba(79, 201, 154, .35);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}
.pd-step-title {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--text-heading);
}
.pd-step.is-done .pd-step-num {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--mo-black);
}

/* ---------------------------------------------------------------- drop zone */
.pd-drop {
  display: grid;
  gap: var(--space-2);
  place-items: center;
  padding: clamp(var(--space-8), 6vw, var(--space-16)) var(--space-6);
  text-align: center;
  cursor: pointer;
  border-style: dashed;
  border-width: 1.5px;
  transition: border-color var(--duration-base) var(--ease-out),
              background var(--duration-base) var(--ease-out);
}
.pd-drop:hover { border-color: rgba(79, 201, 154, .5); background: var(--surface-raised); }
/* is-drag is set by the JS on dragenter. Loud on purpose: the cursor is
   carrying a file and needs to know this is the place to let go. */
.pd-drop.is-drag {
  border-color: var(--accent);
  background: var(--accent-soft);
  border-style: solid;
}
/* A file is chosen. The zone stops advertising and starts reporting. */
.pd-drop.is-loaded {
  border-style: solid;
  border-color: rgba(79, 201, 154, .45);
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--surface-card);
}
.pd-drop.is-loaded .pd-drop-icon { color: var(--accent); }
.pd-drop-icon {
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  line-height: 1;
  color: var(--text-muted);
  transition: color var(--duration-base) var(--ease-out);
}
.pd-drop-title {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--text-heading);
  overflow-wrap: anywhere;
}
.pd-drop-sub {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ------------------------------------------------------------------- fields */
.pd-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: var(--space-4);
}
.pd-field { display: grid; align-content: start; gap: var(--space-2); }
.pd-field label,
.pd-fil-field-label {
  font-family: var(--font-mono);
  font-size: var(--label-size);
  letter-spacing: var(--label-tracking);
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
}
.pd-field input,
.pd-field select,
.pd-field textarea,
.pd-fil-field input {
  width: 100%;
  /* 44px is the floor for a thumb. The old 0.55rem padding made these 32px. */
  min-height: 44px;
  padding: var(--space-3);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  color: var(--text-body);
  font: inherit;
  font-size: var(--text-base);
  transition: border-color var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
}
.pd-field input::placeholder,
.pd-fil-field input::placeholder { color: var(--text-muted); opacity: .7; }
.pd-field input:hover,
.pd-field select:hover { border-color: rgba(255,255,255,.2); }
.pd-field input:focus,
.pd-field select:focus,
.pd-field textarea:focus,
.pd-fil-field input:focus {
  border-color: var(--accent);
  background: var(--surface-card);
}
.pd-field input:disabled,
.pd-field select:disabled { opacity: .5; cursor: not-allowed; }
/* A refused value has to look refused, not merely unfocused. */
.pd-field input.is-bad,
.pd-fil-field input.is-bad { border-color: var(--mo-error); }
/* "optional" beside a label, without shouting it in the same weight. */
.pd-opt {
  font-size: 9.5px;
  letter-spacing: .06em;
  padding: 1px 5px;
  margin-left: var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--surface-raised);
  color: var(--text-muted);
  text-transform: lowercase;
}
/* The <select> behind the slot picker. Visible whenever the picker cannot draw
   — offline, or no slot loaded — because then the select is carrying a status
   sentence and is the better control for it. renderSlotCards() hides this
   wrapper when cards stand in, so one value never has two visible controls. */
.pd-field--select { margin-top: var(--space-1); }

.pd-field select {
  appearance: none;
  /* room for the chevron drawn below */
  padding-right: var(--space-8);
  background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
                    linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

/* -------------------------------------------------------------- slot picker */
/* The colour choice is the most-used control on the site, and it was a <select>
   full of sentences. Now it is what it describes: the four trays, with the
   colour the printer actually read and how much is left on each.
   The <select> stays in the DOM as the value carrier — every send path already
   reads #pd-color — so this is a skin over it, not a replacement. */
.pd-slots {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap: var(--space-3);
}
.pd-slot {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  min-height: 56px;
  text-align: left;
  cursor: pointer;
  background: var(--bg-surface);
  transition: border-color var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
}
.pd-slot:hover:not(:disabled) { border-color: rgba(255,255,255,.24); }
.pd-slot[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.pd-slot:disabled { opacity: .45; cursor: not-allowed; }
.pd-slot-swatch {
  width: 30px; height: 30px;
  border-radius: 8px;
  border: 1px solid rgba(255,255,255,.22);
  flex: none;
}
/* No colour was read off the tag. A checker plate says "unknown" without
   inventing a colour, which showing #000 would. */
.pd-slot-swatch.is-unknown {
  background-color: var(--surface-raised);
  background-image:
    linear-gradient(45deg, rgba(255,255,255,.14) 25%, transparent 25% 75%, rgba(255,255,255,.14) 75%),
    linear-gradient(45deg, rgba(255,255,255,.14) 25%, transparent 25% 75%, rgba(255,255,255,.14) 75%);
  background-size: 10px 10px;
  background-position: 0 0, 5px 5px;
}
.pd-slot-body { display: grid; gap: 3px; min-width: 0; }
.pd-slot-name {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-body);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pd-slot-meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.pd-slot.is-low .pd-slot-meta { color: var(--mo-warning); }
.pd-slot-n {
  position: absolute;
  top: var(--space-2); right: var(--space-2);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-muted);
}
/* A thin fill under each card is a faster read of "how much is left" than a
   percentage, and it sits where a progress bar is expected to. */
.pd-slot-bar {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: rgba(255,255,255,.07);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  overflow: hidden;
}
.pd-slot-bar > i {
  display: block; height: 100%;
  background: var(--accent);
  transition: width var(--duration-base) var(--ease-out);
}
.pd-slot.is-low .pd-slot-bar > i { background: var(--mo-warning); }

/* --------------------------------------------------------------- the machine */
.pd-machine { padding: clamp(var(--space-4), 3vw, var(--space-6)); }
.pd-machine-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
}
.pd-machine-name {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-heading);
}
.pd-machine-model { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-muted); }

/* The live badge: a pill, so "reporting" and "not reporting" are the same shape
   and only the colour moves. */
.pd-live {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 5px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  border: 1px solid rgba(79, 201, 154, .3);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
}
.pd-live .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
  animation: pd-pulse 1.8s var(--ease-out) infinite;
}
@keyframes pd-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
/* Silence is a state, and it gets the error colour rather than a hedge. */
.pd-machine.is-offline .pd-live,
.pd-live.is-offline {
  color: var(--mo-error);
  background: rgba(229, 72, 77, .12);
  border-color: rgba(229, 72, 77, .32);
}
.pd-machine.is-offline .pd-live .dot,
.pd-live.is-offline .dot { animation: none; }

.pd-job {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-3);
}
.pd-job-name {
  font-family: var(--font-display);
  font-size: var(--text-md);
  color: var(--text-body);
  /* min-width alone is not enough. Once .pd-job stacks to a column on a phone,
     a nowrap span is free to be wider than its container — a long Chinese label
     overflowed the viewport by 3px exactly there. max-width is what actually
     hands the ellipsis something to bite on. */
  min-width: 0;
  max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pd-job-pct {
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  line-height: 1;
  color: var(--text-heading);
  font-variant-numeric: tabular-nums;
}
.pd-progress {
  height: 6px;
  border-radius: var(--radius-pill);
  background: rgba(255,255,255,.08);
  overflow: hidden;
}
.pd-progress-bar {
  height: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--accent), var(--accent-strong));
  transition: width var(--duration-base) var(--ease-out);
}
.pd-progress-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* AMS strip on the machine card */
.pd-ams {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.pd-ams-slot {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.pd-ams-slot.is-active {
  border-color: rgba(79, 201, 154, .4);
  color: var(--text-body);
  background: var(--accent-soft);
}
.pd-swatch {
  width: 12px; height: 12px;
  border-radius: 3px;
  border: 1px solid rgba(255,255,255,.25);
  flex: none;
}
.pd-swatch.is-unknown {
  background-color: var(--surface-raised);
  background-image:
    linear-gradient(45deg, rgba(255,255,255,.16) 25%, transparent 25% 75%, rgba(255,255,255,.16) 75%),
    linear-gradient(45deg, rgba(255,255,255,.16) 25%, transparent 25% 75%, rgba(255,255,255,.16) 75%);
  background-size: 6px 6px;
  background-position: 0 0, 3px 3px;
}

/* stat tiles */
.pd-metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(120px, 45%), 1fr));
  gap: var(--space-3);
  margin-top: var(--space-4);
}
.pd-metric {
  display: grid;
  gap: 2px;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
}
.pd-metric-num {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-heading);
  font-variant-numeric: tabular-nums;
}
.pd-metric-label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* cost block */
.pd-cost {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border-subtle);
}
.pd-cost-total {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.pd-cost-formula {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.7;
  color: var(--text-muted);
}

/* ------------------------------------------------------------------ queue */
.pd-queue { padding: clamp(var(--space-4), 3vw, var(--space-6)); }
.pd-queue-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-6);
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-2);
  border-bottom: 1px solid var(--border-subtle);
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.pd-queue-summary strong {
  font-family: var(--font-display);
  font-size: var(--text-md);
  color: var(--text-heading);
  font-weight: var(--weight-semibold);
}
.pd-qrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid rgba(255,255,255,.05);
}
.pd-qrow:last-child { border-bottom: 0; }
.pd-qname { font-size: var(--text-base); color: var(--text-body); }
.pd-qmeta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-left: auto;
}

/* chips — one shape, semantic colour */
.pd-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px var(--space-2);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  color: var(--text-muted);
  white-space: nowrap;
}
.pd-chip--printing,
.pd-chip--next {
  background: var(--accent-soft);
  border-color: rgba(79, 201, 154, .35);
  color: var(--accent);
}
.pd-chip--done { color: var(--text-muted); }
.pd-chip--approval {
  background: rgba(230, 161, 0, .12);
  border-color: rgba(230, 161, 0, .35);
  color: var(--mo-warning);
}
.pd-chip--bad {
  background: rgba(229, 72, 77, .12);
  border-color: rgba(229, 72, 77, .35);
  color: var(--mo-error);
}

/* --------------------------------------------------------- owner: structure */
.pd-owner-stack { display: grid; gap: var(--space-6); }

/* The toolbar sticks, because Refresh is the one control wanted from anywhere
   on a long console. */
.pd-owner-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) clamp(var(--space-3), 3vw, var(--space-4));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--bg-page) 88%, transparent);
  backdrop-filter: blur(10px);
  border: 1px solid var(--border-subtle);
}
@supports not (backdrop-filter: blur(4px)) {
  .pd-owner-bar { background: var(--bg-page); }
}
.pd-updated {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
  margin-right: auto;
}

.pd-group {
  padding: clamp(var(--space-4), 3vw, var(--space-6));
  display: grid;
  gap: var(--space-4);
}
.pd-group-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.pd-group-title {
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--text-heading);
  margin: 0;
}
.pd-group-count {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
}
.pd-group-actions { margin-left: auto; display: flex; gap: var(--space-2); flex-wrap: wrap; }
.pd-empty {
  margin: 0;
  padding: var(--space-6) 0;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.pd-note {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--text-muted);
  max-width: var(--measure-note);
}

/* ------------------------------------------------------ owner: filament card */
.pd-fil { display: grid; gap: var(--space-3); }
.pd-fil-card {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--bg-surface);
}
.pd-fil-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.pd-fil-swatch {
  width: 34px; height: 34px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255,255,255,.22);
  flex: none;
}
.pd-fil-swatch.is-unknown {
  background-color: var(--surface-raised);
  background-image:
    linear-gradient(45deg, rgba(255,255,255,.14) 25%, transparent 25% 75%, rgba(255,255,255,.14) 75%),
    linear-gradient(45deg, rgba(255,255,255,.14) 25%, transparent 25% 75%, rgba(255,255,255,.14) 75%);
  background-size: 10px 10px;
  background-position: 0 0, 5px 5px;
}
.pd-fil-slot {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.pd-fil-name {
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--text-heading);
}
.pd-fil-sub { font-size: var(--text-xs); color: var(--text-muted); }
.pd-fil-remain {
  margin-left: auto;
  text-align: right;
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--text-body);
  font-variant-numeric: tabular-nums;
}
.pd-fil-remain--unknown { color: var(--text-muted); font-size: var(--text-sm); }
.pd-fil-bar {
  height: 5px;
  border-radius: var(--radius-pill);
  background: rgba(255,255,255,.08);
  overflow: hidden;
}
.pd-fil-bar-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--accent);
  transition: width var(--duration-base) var(--ease-out);
}
.pd-fil-card.is-low .pd-fil-bar-fill { background: var(--mo-warning); }
.pd-fil-card.is-low .pd-fil-remain { color: var(--mo-warning); }
.pd-fil-card.is-empty { opacity: .6; }
.pd-fil-tag {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  color: var(--text-muted);
}
/* Who said what is in this slot: the printer's tag, or the owner. */
.pd-fil-tag--read { color: var(--accent); border-color: rgba(79,201,154,.35); }
.pd-fil-tag--owner { color: var(--mo-info); border-color: rgba(91,155,213,.4); }
.pd-fil-stale, .pd-fil-warn { font-size: var(--text-xs); color: var(--mo-warning); margin: 0; }
.pd-fil-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.pd-fil-field { display: grid; gap: var(--space-2); }
.pd-fil-hint {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--text-muted);
  max-width: var(--measure-note);
}

/* ------------------------------------------------------- owner: job cards */
.pd-jobcard {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--bg-surface);
  margin-bottom: var(--space-3);
}
.pd-jobcard:last-child { margin-bottom: 0; }
.pd-jobcard-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
}
.pd-jobcard-file {
  font-size: var(--text-base);
  color: var(--text-heading);
  font-weight: var(--weight-medium);
  overflow-wrap: anywhere;
}
.pd-jobcard-time,
.pd-jobcard-meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.pd-jobcard-meta { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.pd-jobcard-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.pd-jobcard-note {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--text-muted);
}
.pd-mono { font-family: var(--font-mono); overflow-wrap: anywhere; }

/* the drag grip — a real target, not a 12px glyph */
.pd-grip {
  flex: none;
  width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: var(--radius-md);
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-muted);
  cursor: grab;
  touch-action: none;
}
.pd-grip:hover { background: var(--surface-raised); border-color: var(--border-subtle); color: var(--text-body); }
.pd-grip--locked { opacity: .3; cursor: not-allowed; }
.pd-grip-dots { pointer-events: none; font-family: var(--font-mono); line-height: .7; font-size: 13px; letter-spacing: 1px; }
.pd-jobcard.is-dragging,
.pd-jobcard.pd-dragging {
  border-color: var(--accent);
  box-shadow: var(--shadow-raised);
  cursor: grabbing;
}
.pd-jobcard.is-shifting { transition: transform var(--duration-base) var(--ease-out); }

/* ------------------------------------------------------ owner: send control */
.pd-send-control {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
}
.pd-send-line { font-size: var(--text-sm); color: var(--text-body); margin: 0; }
.pd-send-line--ok { color: var(--accent); }
.pd-send-line--warn, .pd-send-line--stale { color: var(--mo-warning); }
.pd-send-line--bad { color: var(--mo-error); }
.pd-send-sub { font-size: var(--text-xs); color: var(--text-muted); margin: 0; }
.pd-send-sub--warn { color: var(--mo-warning); }
.pd-send-sub--bad { color: var(--mo-error); }
.pd-send-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.pd-send-countdown {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--mo-warning);
}
.pd-send-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-body);
  cursor: pointer;
  padding: var(--space-2) 0;
}
.pd-send-check input { width: 20px; height: 20px; flex: none; accent-color: var(--accent); margin-top: 1px; }
.pd-send--live .pd-send-line { color: var(--accent); }
.pd-send--sent .pd-send-line { color: var(--accent); }
.pd-send--failed .pd-send-line { color: var(--mo-error); }
.pd-send--unknown .pd-send-line { color: var(--mo-warning); }
.pd-send-tag--live { color: var(--accent); }
.pd-send-tag--failed { color: var(--mo-error); }
.pd-send-tag--unknown { color: var(--mo-warning); }
.pd-send-blind { font-size: var(--text-xs); color: var(--mo-warning); margin: 0; }

/* slice state */
.pd-slice-headline {
  font-family: var(--font-display);
  font-size: var(--text-md);
  color: var(--text-heading);
  margin: 0;
  font-variant-numeric: tabular-nums;
}
.pd-slice-line { font-size: var(--text-sm); color: var(--text-body); margin: 0; }
.pd-slice-sub { font-size: var(--text-xs); color: var(--text-muted); margin: 0; }
.pd-slice--slicing .pd-slice-line { color: var(--accent); }
.pd-slice--failed .pd-slice-line { color: var(--mo-error); }
.pd-slice--skipped .pd-slice-line { color: var(--mo-warning); }
.pd-slice-pulse {
  display: inline-block;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
  margin-right: var(--space-2);
  animation: pd-pulse 1.4s var(--ease-out) infinite;
}

/* ------------------------------------------------------------ owner: camera */
.pd-cam { display: grid; gap: var(--space-3); }
.pd-cam-frame {
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--mo-black);
  border: 1px solid var(--border-subtle);
}
.pd-cam-img { display: block; width: 100%; height: auto; }
.pd-cam-when {
  position: absolute;
  bottom: var(--space-2); right: var(--space-2);
  padding: 3px var(--space-2);
  border-radius: var(--radius-sm);
  background: rgba(0,0,0,.65);
  backdrop-filter: blur(4px);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-body);
}

/* ------------------------------------------------------------- owner: gate */
.pd-gate { display: grid; place-items: center; padding: var(--space-12) 0; }
.pd-gate-form {
  display: grid;
  gap: var(--space-4);
  width: 100%;
  max-width: 380px;
  padding: clamp(var(--space-6), 5vw, var(--space-8));
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-raised);
}
.pd-gate-note {
  margin: 0;
  font-size: var(--text-xs);
  line-height: var(--leading-body);
  color: var(--text-muted);
}
.pd-check {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-body);
  cursor: pointer;
  min-height: 40px;
}
.pd-check input { width: 20px; height: 20px; flex: none; accent-color: var(--accent); }
.pd-btn-block { width: 100%; }

/* a refusal, and the reason */
.pd-lock {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: rgba(229, 72, 77, .1);
  border: 1px solid rgba(229, 72, 77, .3);
  color: var(--text-body);
  font-size: var(--text-sm);
}
.pd-cannot {
  margin: 0;
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-left: 2px solid var(--mo-warning);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--text-muted);
  max-width: var(--measure-note);
}
.pd-cannot strong { color: var(--text-body); }

/* ------------------------------------------------------------------ toasts */
.pd-toast {
  display: none;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid rgba(79, 201, 154, .4);
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-4);
  margin: 0;
}
.pd-toast.is-on { display: inline-block; }
/* Failures must never wear the accent: a rejected code or a dead hub has to
   read as a failure at a glance, not as a confirmation. */
.pd-toast--error {
  color: var(--mo-error);
  background: rgba(229, 72, 77, .12);
  border-color: rgba(229, 72, 77, .4);
}
.pd-toast--fixed {
  position: fixed;
  left: 50%;
  bottom: max(var(--space-6), env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 60;
  box-shadow: var(--shadow-raised);
  background: var(--surface-raised);
  max-width: min(92vw, 460px);
  text-align: center;
}
.pd-toast--fixed.is-on { display: block; }

/* ------------------------------------------------------------------ actions */
.pd-form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
}
.pd-btn-tap { min-height: 44px; }
.pd-danger { color: var(--mo-error); border-color: rgba(229, 72, 77, .4); }
.pd-danger:hover { background: rgba(229, 72, 77, .12); }

/* closed list */
.pd-closed > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  font-family: var(--font-display);
  font-size: var(--text-base);
  color: var(--text-body);
  min-height: 56px;
}
.pd-closed > summary::-webkit-details-marker { display: none; }
.pd-closed[open] > summary { border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
.pd-closed > div {
  padding: var(--space-4);
  border: 1px solid var(--border-subtle);
  border-top: 0;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* Two doors into the network: ask to be added, or set up a machine you already
   have a code for. Side by side because the difference between them is the only
   thing a reader has to decide, and it should not need a paragraph. */
.pd-doors {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: var(--space-4);
}
.pd-door {
  display: grid;
  align-content: start;
  gap: var(--space-3);
  padding: clamp(var(--space-4), 3vw, var(--space-6));
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
}
.pd-door--accent {
  border-color: rgba(79, 201, 154, .32);
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--surface-card);
}
.pd-door-tag {
  font-family: var(--font-mono);
  font-size: var(--label-size);
  letter-spacing: var(--label-tracking);
  text-transform: uppercase;
  color: var(--text-muted);
}
.pd-door--accent .pd-door-tag { color: var(--accent); }
.pd-door-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-heading);
}
.pd-door .pd-form-actions { margin-top: var(--space-2); }
.pd-door .pd-plus-wrap { margin-top: var(--space-2); }

/* add-a-printer */
.pd-plus-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}
.pd-plus {
  width: 56px; height: 56px;
  flex: none;
  display: grid; place-items: center;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  border: 1px solid rgba(79, 201, 154, .4);
  color: var(--accent);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  line-height: 1;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}
.pd-plus:hover { background: rgba(79, 201, 154, .22); }
.pd-plus[aria-expanded="true"] { transform: rotate(45deg); }
.pd-form { display: none; margin-top: var(--space-6); }
.pd-form.is-open { display: block; }

/* --------------------------------------------------------------- responsive */
@media (max-width: 640px) {
  .pd-job { align-items: flex-start; flex-direction: column; gap: var(--space-2); }
  .pd-job-pct { font-size: var(--text-xl); }
  .pd-fil-head { flex-wrap: wrap; }
  .pd-fil-remain { margin-left: 0; text-align: left; }
  .pd-owner-bar { gap: var(--space-2); }
  .pd-updated { flex: 1 0 100%; margin-right: 0; }
  /* Every button in a row on a phone should be reachable with one thumb rather
     than a horizontal scroll. */
  .pd-fil-actions > .btn,
  .pd-send-actions > .btn,
  .pd-jobcard-actions > .btn { flex: 1 1 auto; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  .pd-live .dot,
  .pd-slice-pulse { animation: none; }
  .pd-drop, .pd-slot, .pd-progress-bar, .pd-fil-bar-fill,
  .pd-plus, .pd-why > summary::before { transition: none; }
}

/* ============================================================================
   APPENDED BLOCK — the three controls the owner console did not have: the state
   of a job as one row, stopping a running print, and removing the machine.

   Everything above this line is untouched. These are new class names only; no
   rule in here renames or overrides anything that was already emitting.

   A NOTE ON ONE NAME. There is no `.pd-state-line` in this block and there must
   not be: /printdrop/job carries an ELEMENT with id pd-state-line and loads this
   same stylesheet, and a class of that name here would be one grep away from
   being mistaken for it.

   THE COLOUR ARGUMENT, since this file already made the opposite one for sends.
   The send control is amber because it is "a machine about to move while nobody
   is standing next to it" — not an error and not a refusal. A stop is a
   different thing again: nothing is going wrong, but something is about to be
   destroyed, namely the part on the plate. That is what red already means on
   this page (pd-danger, Reject, Mark failed), so a stop is red — and it keeps
   the send control's accident-proof geometry, because the geometry was never
   about the colour.
   ========================================================================= */

/* ------------------------------------------------ the state row on every card */
/* Where the job is, and everywhere it can go from there, in one band. It gets a
   rule above it for the same reason the send region does: it is a group of
   controls rather than more card, and the eye should find its edge without
   reading it. */
.pd-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
  min-width: 0;
}
.pd-state-label {
  font-family: var(--font-mono);
  font-size: var(--label-size);
  letter-spacing: var(--label-tracking);
  text-transform: uppercase;
  color: var(--text-muted);
}
/* The current state, wearing the same chip the card head, the closed list and
   the public queue wear. The vocabulary is learned once. */
.pd-state-now { flex: none; }
.pd-state-arrow {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text-muted);
}
/* Destinations. Compact, but never below the 44px this file holds everything
   else to — four of these are tapped standing up, one-handed, next to a machine.
   pd-btn-tap already sets the height; what is set here is the shape. */
.pd-state-pill {
  padding-inline: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: .04em;
}
/* Forward is the site's ordinary secondary button: neutral, because queueing or
   marking a job started is not a claim about anything. */
.pd-state-pill--ok { color: var(--accent); border-color: rgba(79, 201, 154, .4); }
.pd-state-pill--ok:hover { background: var(--accent-soft); }
.pd-state-pill--bad { color: var(--mo-error); border-color: rgba(229, 72, 77, .4); }
.pd-state-pill--bad:hover { background: rgba(229, 72, 77, .12); }
/* In the closed list the row already opens with the chip, so the group is the
   tail of a line rather than a band of its own. */
.pd-qrow > .pd-state {
  padding-top: 0;
  border-top: 0;
  flex: 0 1 auto;
}

/* --------------------------------------------------------- stopping a print */
.pd-stop {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-top: var(--space-4);
  border-top: 1px dashed var(--border-subtle);
  min-width: 0;
}
/* The rule takes the colour of the outcome, so the block is placed before a word
   of it is read — the same trick the send region plays. */
.pd-stop--requested,
.pd-stop--stopping { border-top-color: var(--mo-warning); }
.pd-stop--stopped { border-top-color: var(--accent); }
.pd-stop--stop_failed { border-top-color: rgba(229, 72, 77, .5); }

.pd-stop-tag {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  padding: 3px var(--space-2);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .14em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--text-muted);
}
.pd-stop-tag--live { border-color: var(--mo-warning); color: var(--mo-warning); }
.pd-stop-tag--done { border-color: rgba(79, 201, 154, .5); color: var(--accent); }
.pd-stop-tag--bad { border-color: rgba(229, 72, 77, .5); color: var(--mo-error); }

.pd-stop-control {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}
/* Deliberately not .btn, for the same reason the send button is not: no shared
   class, so no future restyle of the button family can quietly restyle the one
   control on the page that halts a machine. */
.pd-stop-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  width: 100%;
  /* Full width on a phone, where it is the card. Capped on a laptop, where a
     900px red bar stops reading as a button and starts reading as an alarm. */
  max-width: 360px;
  min-height: 48px;
  padding: 10px var(--space-4);
  border: 1px solid rgba(229, 72, 77, .55);
  border-radius: var(--radius-md);
  background: rgba(229, 72, 77, .10);
  color: var(--mo-error);
  font-family: var(--font-mono);
  font-size: 12.5px;
  letter-spacing: .1em;
  line-height: 1.35;
  text-transform: uppercase;
  text-align: center;
  cursor: pointer;
  overflow-wrap: anywhere;
}
/* A square, where the send button carries a dot. Two controls that both move the
   machine should not be told apart by colour alone — a red dot and an amber dot
   are the same shape to somebody who cannot see the difference. */
.pd-stop-btn::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  background: currentColor;
}
.pd-stop-btn:hover { background: rgba(229, 72, 77, .18); }
.pd-stop-btn:disabled { opacity: .5; cursor: default; }

.pd-stop-confirm {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid rgba(229, 72, 77, .55);
  border-radius: var(--radius-md);
  background: rgba(229, 72, 77, .06);
  max-width: var(--measure-note);
  min-width: 0;
}
/* display:flex would beat the hidden attribute, and a confirmation that is
   always open is not a confirmation. The send panel needs the same line. */
.pd-stop-confirm[hidden] { display: none; }

/* ------------------------------------------------- [hidden] has to be re-won
   `[hidden]` is `display: none` in the USER-AGENT stylesheet only, so any author
   rule that sets `display` on the same element beats it outright — there is no
   specificity contest, the UA sheet simply loses. The element is then permanently
   visible and the JS flipping `.hidden` on it does nothing whatsoever.

   That is how the owner console came to announce "THIS MACHINE IS GONE" on every
   single load: `.pd-gone` sets `display: grid` and nothing put the guard back. The
   panel was never wired to anything but a completed removal — it was the stylesheet
   saying a machine had been deleted, about a printer that was sitting there
   printing. site.css guards six classes this way and printdrop.css one; these are
   the seven that were missed, found by sweeping every element in the PrintDrop
   pages that ships with a bare `hidden` attribute.

   ANY new `display:` rule for a class that appears on a `hidden` element needs its
   name in this list. */
.pd-gone[hidden],
.pd-metrics[hidden],
.pd-slice[hidden],
.pd-jobcard[hidden],
.pd-fields[hidden],
.pd-group[hidden],
.pd-drop-icon[hidden],
/* Added 2026-08-12, having done the exact thing the paragraph above warns
   about. The describe-a-part panel shipped with six new `display: grid` rules
   and none of these names, so `hidden` lost to every one of them: the whole
   panel rendered at 475x972 on the live page with all four phases stacked at
   once -- the ask box, a spinner reading "Drawing it. This takes a minute or
   two.", a src-less <img alt="The part that was drawn">, and an ENABLED "Send
   it to the queue" -- and the toggle button appeared to do nothing.
   It survived testing because the tests asked the DOM whether `.hidden` was
   true, which it faithfully was, instead of asking the renderer whether
   anything was on screen. A property is not a pixel. */
.pd-gen[hidden],
.pd-gen-ask[hidden],
.pd-gen-wait[hidden],
.pd-gen-result[hidden],
.pd-gen-failed[hidden],
.pd-gen-found[hidden],
.pd-gen-block[hidden],
/* .pd-gen-think is `display: grid` below, and it starts hidden on every load --
   without this it would sit under the spinner as an empty gap for ever. Added
   in the same edit as the rule, which is the only way this list stays true. */
.pd-gen-think[hidden],
/* .pd-gen-thumbs is `display: flex` below and starts hidden with no photos in
   it. Same edit as the rule, same reason. */
.pd-gen-thumbs[hidden] { display: none; }
/* Bambu's own question, at the size of a question. */
.pd-stop-ask {
  margin: 0;
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--text-heading);
}
.pd-stop-leaves {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11.5px;
  line-height: 1.7;
  color: var(--mo-warning);
  max-width: var(--measure-note);
}
/* Filled, like the send control's go button and for the same reason: it is the
   one control in the panel that does something in the physical world. */
.pd-stop-go {
  flex: 1 1 200px;
  max-width: 340px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 10px var(--space-4);
  border: 1px solid var(--mo-error);
  border-radius: var(--radius-md);
  background: var(--mo-error);
  color: var(--mo-white, #fff);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: var(--weight-semibold);
  line-height: 1.35;
  text-align: center;
  cursor: pointer;
  overflow-wrap: anywhere;
}
.pd-stop-go:hover { background: #ef5f63; }
.pd-stop-btn:focus-visible,
.pd-stop-go:focus-visible,
.pd-state-pill:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ------------------------------------------------------- removing the machine */
/* Unglamorous on purpose. No red until the slug has been typed: a panel that
   shouts before it is armed teaches the eye to skip it, which is the opposite of
   what a control like this needs. */
.pd-remove { border-color: var(--border-subtle); }
.pd-remove .pd-qrow:last-child { border-bottom: 0; }
.pd-remove #pd-remove-slug {
  font-family: var(--font-mono);
  max-width: 26ch;
}
/* Armed. The button carries the colour only when it can actually do something,
   so the state of the field is legible from the button across a room. */
#pd-remove-go:disabled { opacity: .45; cursor: not-allowed; }
#pd-remove-go:not(:disabled) {
  background: rgba(229, 72, 77, .12);
  border-color: var(--mo-error);
  color: var(--mo-error);
}

/* The receipt, for a machine that no longer exists. It is all that is left on
   the page, so it is centred like the gate rather than left in a column beside
   nothing. */
.pd-gone {
  display: grid;
  gap: var(--space-4);
  width: 100%;
  max-width: 560px;
  margin: var(--space-12) auto;
  padding: clamp(var(--space-6), 5vw, var(--space-8));
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  border: 1px solid rgba(229, 72, 77, .3);
  font-variant-numeric: tabular-nums;
}
.pd-gone .pd-qrow:last-child { border-bottom: 0; }

@media (max-width: 640px) {
  /* Same rule the other button rows get: reachable with one thumb rather than
     with a horizontal scroll. */
  .pd-state > .btn { flex: 1 1 auto; justify-content: center; }
  .pd-remove #pd-remove-slug { max-width: 100%; }
}

/* ------------------------------------------------- a short numbered how-to */
/* For the one place a control needs instructions rather than an explanation:
   setting up an ntfy topic means doing something in another app, and no amount
   of prose replaces four numbered steps. Kept visible on purpose — this is the
   exception to the rule that reasoning goes in a .pd-why, because it is not
   reasoning, it is what to press. */
.pd-steps-lite {
  margin: 0;
  padding-left: var(--space-6);
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--text-body);
  max-width: var(--measure-note);
}
.pd-steps-lite li { padding-left: var(--space-1); }
.pd-steps-lite li::marker {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--accent);
}
.pd-steps-lite strong { color: var(--text-heading); }
.pd-steps-lite em { color: var(--accent); font-style: normal; }

/* ------------------------------------------- the four secrets, shown once */
/* A join now creates the machine, so the response carries credentials that
   cannot be asked for again. They get their own block rather than a toast,
   because a toast disappears and these have to be written down. */
.pd-secrets { display: grid; gap: var(--space-2); margin: var(--space-2) 0; }
.pd-secret {
  display: grid;
  grid-template-columns: minmax(120px, auto) 1fr;
  gap: 2px var(--space-3);
  align-items: baseline;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
}
.pd-secret-label {
  font-family: var(--font-mono);
  font-size: var(--label-size);
  letter-spacing: var(--label-tracking);
  text-transform: uppercase;
  color: var(--text-muted);
}
.pd-secret-value {
  font-size: var(--text-base);
  color: var(--text-heading);
  overflow-wrap: anywhere;
  -webkit-user-select: all; user-select: all;   /* one tap selects the whole code */
}
.pd-secret-note {
  grid-column: 2;
  font-size: var(--text-xs);
  color: var(--text-muted);
}
@media (max-width: 520px) {
  .pd-secret { grid-template-columns: 1fr; }
  .pd-secret-note { grid-column: 1; }
}

/* ------------------------------------------------- the code friends type
   Two tier pills and a live reading of what a friend's typing reduces to.
   Real radios underneath, so a keyboard and a screen reader get a radiogroup
   rather than a set of divs that happen to look like one. */
.pd-code-tiers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
  gap: var(--space-2);
  margin: 0 0 var(--space-1);
}
.pd-code-tier { cursor: pointer; }
.pd-code-tier > input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.pd-code-tier > span {
  display: grid;
  gap: 2px;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--surface-card);
  font-size: var(--text-sm);
  color: var(--text-body);
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.pd-code-tier > span > small {
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.pd-code-tier:hover > span { border-color: rgba(255, 255, 255, .24); }
.pd-code-tier > input:checked + span {
  border-color: rgba(79, 201, 154, .45);
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), var(--surface-card);
  color: var(--text-heading);
}
/* Focus has to live on the LABEL's span, because the input it belongs to is
   visually gone — without this the radiogroup is keyboard-invisible. */
.pd-code-tier > input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.pd-code-read {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.pd-code-read.is-bad { color: var(--mo-warning); }
.pd-code-read.is-good { color: var(--accent); }

/* --------------------------------------------------- the slicer's plate render
   Bambu draws the finished plate into the 3mf it exports; this is that picture,
   shown on a sliced job card so the owner can see which way up the part went
   before pressing Send. Transparent PNG on a plate-coloured tile, because the
   render has no background of its own and a bare image floats. */
.pd-plate {
  margin: 0;
  display: grid;
  gap: var(--space-2);
  justify-items: center;
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background:
    linear-gradient(var(--accent-soft), var(--accent-soft)),
    var(--surface-sunken, var(--surface-card));
}
.pd-plate-img {
  display: block;
  width: 100%;
  max-width: 240px;
  height: auto;
  /* The render is square; reserving the box stops the card jumping when it
     arrives, which on a queue of several jobs is the whole list moving. */
  aspect-ratio: 1 / 1;
  object-fit: contain;
}
/* ------------------------------------------- the contact field's explainer */
/* Hover on a mouse, tap on a thumb, focus on a keyboard — three ways in, one
   panel. Deliberately NOT built on [hidden]: the panel needs a display value of
   its own, any author display rule beats the user-agent sheet's [hidden], and
   that collision is how a panel ends up on screen before anything opened it.
   Visibility and opacity instead, which animate and cannot be overridden by
   accident. */
.pd-label-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.pd-tip { position: relative; display: inline-flex; }
.pd-tip-btn {
  display: grid;
  place-items: center;
  /* 24px is small for a thumb, so the tap area is widened past the ink with a
     pseudo-element below rather than by drawing a bigger circle. */
  width: 24px; height: 24px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--border-subtle);
  background: var(--surface-card);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.pd-tip-btn::after {
  content: "";
  position: absolute;
  inset: -10px;            /* 44px total, which is the tap target */
}
.pd-tip-btn:hover, .pd-tip-btn:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
}
.pd-tip-pop {
  position: absolute;
  top: calc(100% + var(--space-2));
  left: 0;
  z-index: 30;
  width: max-content;
  max-width: min(320px, calc(100vw - 2 * var(--space-4)));
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--surface-card);
  box-shadow: 0 12px 32px rgba(0, 0, 0, .45);
  font-size: var(--text-xs);
  line-height: 1.5;
  color: var(--text-body);
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity .12s ease, transform .12s ease, visibility 0s .12s;
  pointer-events: none;
}
/* Tap, and keyboard focus. Works on every device, and is the ONLY path on a
   touch screen — where :hover either never fires or, worse, sticks after a tap
   and leaves the panel open with no way to dismiss it. */
.pd-tip.is-open .pd-tip-pop,
.pd-tip-btn:focus-visible + .pd-tip-pop {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
  pointer-events: auto;
}
/* And hover, but only where hovering is a real thing a pointer can do. Without
   this query a phone gets the hover rule too, via the emulated hover that fires
   on tap and then never clears. */
@media (hover: hover) and (pointer: fine) {
  .pd-tip:hover .pd-tip-pop {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition-delay: 0s;
    pointer-events: auto;
  }
}
@media (prefers-reduced-motion: reduce) {
  .pd-tip-pop { transition: none; transform: none; }
}

/* ------------------------------------- the friend's "push to my phone" setup */
/* Sits under the contact field on the drop page. The button is quiet until it is
   the thing to press, and the steps that replace it are the only place on any
   PrintDrop page where a credential is shown to be copied. */
.pd-ntfy-cta {
  justify-self: start;
  margin-top: var(--space-2);
  font-size: var(--text-sm);
}
.pd-ntfy-steps {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--surface-sunken, var(--surface-card));
  display: grid;
  gap: var(--space-2);
}
/* `[hidden]` is display:none in the USER-AGENT sheet only, and the rule above
   sets display:grid — which beats it. Without this the steps are on screen
   before the button that reveals them has been pressed. The same trap the
   console's panels were swept for. */
.pd-ntfy-steps[hidden] { display: none; }
/* The topic itself: a credential, on a line of its own so it can be read off a
   phone screen and typed into another app without losing a character. Breaks
   anywhere rather than overflowing — 23 characters do not fit a 375px screen
   beside a Copy button. */
.pd-ntfy-mine {
  display: inline-block;
  margin: var(--space-2) var(--space-2) var(--space-2) 0;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  color: var(--accent);
  overflow-wrap: anywhere;
  user-select: all;   /* one tap selects the whole thing, for a manual copy */
}

/* The slicer's render is square; the printer's own camera is not. A still and a
   timelapse are both 1280x720 off the RTSPS stream, so forcing them into the
   square box above letterboxes them into something smaller than the render they
   are meant to replace. */
.pd-shot .pd-plate-img,
.pd-film .pd-plate-img {
  max-width: 360px;
  aspect-ratio: 16 / 9;
}
/* A <video> has no `alt` to fall back on and shows the page background through
   any letterboxing, which reads as a broken element rather than a film with
   bars. Black is what a video player looks like. */
.pd-film .pd-plate-img {
  background: #000;
  border-radius: var(--radius-sm);
}
.pd-plate-cap {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  text-align: center;
}

/* ===========================================================================
   The second door: describing a part instead of uploading one.
   ---------------------------------------------------------------------------
   REBUILT after the first version measured 11 bordered boxes and 3 background
   surfaces inside a single panel, against this file's own rule four hundred
   lines up: "One surface recipe, so twelve panels do not each invent a border."
   The panel is now ONE surface, and everything inside separates by type and
   space rather than by chrome. Two things keep a border: the render, because it
   sits on the renderer's cream ground and needs an edge against a dark page,
   and a text field, because something you type into should look like it.

   The other half of the rebuild is weight. Three equal buttons at the decision
   made "Bin it" look like a peer of "Send it to the queue"; the primary action
   is now the only filled thing on screen and the other two are quiet.
   ======================================================================== */

/* ---- the door ---------------------------------------------------------- */

.pd-gen-open {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  margin-top: var(--space-2);
  padding: var(--space-3) var(--space-4);
  text-align: left;
  font: inherit;
  cursor: pointer;
  color: var(--text-body);
  background: transparent;
  border: 1px dashed var(--border-subtle);
  border-radius: var(--radius-lg);
  transition: border-color .15s ease, color .15s ease;
}
/* Dashed and quiet: the file drop above is the main way in, and this is the
   alternative rather than its competitor. */
.pd-gen-open:hover { border-color: var(--accent); color: var(--text-heading); }
.pd-gen-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pd-gen-open[aria-expanded="true"] {
  border-style: solid;
  border-color: var(--border-subtle);
  border-bottom-color: transparent;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--surface-card);
}
.pd-gen-open-icon { font-size: var(--text-md); color: var(--accent); line-height: 1; }
.pd-gen-open-text { display: grid; gap: 1px; flex: 1; min-width: 0; }
.pd-gen-open-title { font-size: var(--text-sm); color: var(--text-heading); font-weight: 600; }
.pd-gen-open-sub { font-size: var(--text-xs); color: var(--text-muted); }
.pd-gen-open-chev {
  font-size: var(--text-md); color: var(--text-muted); transition: transform .18s ease;
}
.pd-gen-open[aria-expanded="true"] .pd-gen-open-chev { transform: rotate(90deg); }

/* ---- the one surface --------------------------------------------------- */

.pd-gen {
  display: grid;
  gap: 20px;
  padding: var(--space-4);
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-top: 0;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* ---- asking ------------------------------------------------------------ */

.pd-gen-ask { display: grid; gap: var(--space-3); }
.pd-gen-label {
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-muted); font-weight: 600;
}
.pd-gen-text {
  width: 100%;
  min-height: 92px;
  resize: vertical;
  font: inherit;
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--text-body);
  padding: var(--space-3);
  background: var(--bg-page);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  transition: border-color .15s ease;
}
.pd-gen-text::placeholder { color: var(--text-muted); }
.pd-gen-text:hover { border-color: var(--text-muted); }
.pd-gen-text:focus-visible { outline: none; border-color: var(--accent); }
/* One line, not a paragraph. The first version put 33 words above an empty box. */
.pd-gen-tip { margin: 0; font-size: var(--text-xs); color: var(--text-muted); }
.pd-gen-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.pd-gen-note { font-size: var(--text-xs); color: var(--mo-warning); margin: 0; }
.pd-gen-note--ok { color: var(--accent); }

/* ---- buttons: exactly one filled thing at a time ----------------------- */

.pd-gen-go, .pd-gen-accept, .pd-gen-retry {
  font: inherit; font-size: var(--text-sm); font-weight: 600; cursor: pointer;
  padding: 11px var(--space-6);
  color: var(--mo-black);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--radius-pill);
  transition: background .15s ease, border-color .15s ease;
}
.pd-gen-go:hover, .pd-gen-accept:hover, .pd-gen-retry:hover {
  background: var(--mo-accent-strong); border-color: var(--mo-accent-strong);
}
.pd-gen-go[disabled], .pd-gen-accept[disabled] { opacity: .45; cursor: default; }
.pd-gen-redraw {
  font: inherit; font-size: var(--text-sm); font-weight: 600; cursor: pointer;
  padding: 9px var(--space-4);
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--accent);
  border-radius: var(--radius-pill);
  justify-self: start;
}
.pd-gen-redraw:hover { background: var(--mo-accent-soft); }
.pd-gen-redraw[disabled] { opacity: .45; cursor: default; }
/* Quiet on purpose. These were bordered buttons the same size as the primary,
   which made binning the part look as considered a choice as printing it. */
.pd-gen-again, .pd-gen-drop, .pd-gen-cancel {
  font: inherit; font-size: var(--text-sm); cursor: pointer;
  padding: 11px var(--space-2);
  color: var(--text-muted);
  background: none;
  border: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--border-subtle);
}
.pd-gen-again:hover, .pd-gen-drop:hover, .pd-gen-cancel:hover {
  color: var(--text-heading); text-decoration-color: currentColor;
}
.pd-gen-go:focus-visible, .pd-gen-accept:focus-visible, .pd-gen-redraw:focus-visible,
.pd-gen-retry:focus-visible, .pd-gen-again:focus-visible, .pd-gen-drop:focus-visible,
.pd-gen-cancel:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- waiting ----------------------------------------------------------- */

.pd-gen-wait {
  display: grid; gap: var(--space-3); justify-items: center;
  padding: var(--space-12) var(--space-4);
  text-align: center;
}
.pd-gen-spin {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--border-subtle);
  border-top-color: var(--accent);
  animation: pd-gen-turn 900ms linear infinite;
}
@keyframes pd-gen-turn { to { transform: rotate(360deg); } }
.pd-gen-wait-head { margin: 0; font-size: var(--text-sm); color: var(--text-heading); }
/* The joke is the thing being read for two minutes, so it is set at reading
   size rather than as fine print, and given a fixed height so the panel does
   not jump every time one is longer than the last. */
.pd-gen-joke {
  margin: 0; min-height: 3em; max-width: 34ch;
  font-size: var(--text-base); color: var(--text-muted); line-height: 1.5;
  animation: pd-gen-fade 600ms ease;
}
@keyframes pd-gen-fade { from { opacity: 0; } to { opacity: 1; } }

/* The model thinking out loud. Same font size and min-height as the joke it
   replaces, so the swap costs about 20px of shift for the eyebrow rather than
   re-laying-out the panel. It is left-aligned and given a full 42ch because it
   is prose with millimetres in it, not a one-line quip. */
.pd-gen-think {
  display: grid;
  margin: 0; min-height: 3em; max-width: 42ch;
  font-size: var(--text-base); line-height: 1.5;
  /* --text-body, not --text-muted: once this replaces the joke it IS the
     content of the panel, and a muted grey would read as a footnote to a
     spinner. (There is no --text-secondary in tokens.css -- a var() fallback
     to it looked considered and resolved to the muted grey anyway.) */
  color: var(--text-body);
  text-align: left;
  animation: pd-gen-fade 400ms ease;
}
.pd-gen-think::before {
  content: 'Working it out';
  font-size: var(--text-xs); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--mo-accent); margin-bottom: var(--space-1);
}

/* ---- photos ------------------------------------------------------------ */
/* Sits between the sentence and the button, because that is the order a person
   does it in: say what you want, show it, then ask for it. */
.pd-gen-pics {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2) var(--space-3);
}
.pd-gen-add {
  border: 1px dashed var(--line-soft, rgba(255, 255, 255, 0.22));
  background: transparent; color: var(--text-body);
  border-radius: var(--radius-pill, 999px);
  padding: var(--space-2) var(--space-4);
  font: inherit; font-size: var(--text-sm); cursor: pointer;
  transition: border-color 140ms ease, color 140ms ease;
}
.pd-gen-add:hover { border-color: var(--mo-accent); color: var(--mo-accent); }
.pd-gen-add:disabled { opacity: 0.45; cursor: default; border-style: solid; }
.pd-gen-pics-tip { font-size: var(--text-xs); color: var(--text-muted); }
.pd-gen-thumbs {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  list-style: none; margin: 0; padding: 0; width: 100%;
}
.pd-gen-thumb { position: relative; }
.pd-gen-thumb img {
  display: block; width: 72px; height: 72px; object-fit: cover;
  border-radius: var(--radius-sm, 6px); border: 1px solid var(--line-soft, rgba(255,255,255,0.16));
}
/* The remove control is a real button, not an ✕ glyph on a div: a photo of the
   inside of somebody's house is exactly the thing they may want to take back
   after looking at the thumbnail. */
.pd-gen-thumb-x {
  position: absolute; top: -6px; right: -6px;
  width: 22px; height: 22px; border-radius: 50%;
  border: none; cursor: pointer; line-height: 1;
  background: var(--surface-card, #1b1f1e); color: var(--text-body);
  box-shadow: 0 0 0 1px var(--line-soft, rgba(255,255,255,0.24));
}
.pd-gen-thumb-x:hover { color: var(--mo-accent); }

/* ---- the answer -------------------------------------------------------- */

.pd-gen-result { display: grid; gap: var(--space-4); }
/* Full bleed inside the panel. It is the answer, not an illustration beside it. */
.pd-gen-shot {
  margin: calc(var(--space-4) * -1) calc(var(--space-4) * -1) 0;
  border-bottom: 1px solid var(--border-subtle);
  background: #FFFFE5;
  overflow: hidden;
}
.pd-gen-shot img { display: block; width: 100%; height: auto; }
.pd-gen-summary {
  margin: 0; font-size: var(--text-md); color: var(--text-heading); line-height: 1.5;
}

/* A quiet row of numbers rather than a card. */
.pd-gen-facts {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-6); margin: 0;
}
.pd-gen-facts > div { display: grid; gap: 1px; min-width: 0; }
.pd-gen-facts dt {
  font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--text-muted);
}
.pd-gen-facts dd {
  margin: 0; font-size: var(--text-base); color: var(--text-body);
  font-variant-numeric: tabular-nums;
}
.pd-gen-facts dd.pd-gen-good { color: var(--accent); }
.pd-gen-facts dd.pd-gen-warn { color: var(--mo-warning); }

.pd-gen-block { display: grid; gap: var(--space-2); }
.pd-gen-h {
  margin: 0; font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-muted); font-weight: 600;
}
.pd-gen-sub { margin: 0; font-size: var(--text-xs); color: var(--text-muted); }
/* An accent rule instead of a box. These are what the person is meant to read
   before deciding, so they get emphasis without another border. */
.pd-gen-concerns {
  margin: 0; padding: 0 0 0 var(--space-4);
  border-left: 2px solid var(--mo-accent-soft);
  list-style: none;
  display: grid; gap: var(--space-2);
  font-size: var(--text-sm); color: var(--text-body); line-height: 1.5;
}

/* ---- the numbers a person can change ----------------------------------- */

.pd-gen-params { display: grid; gap: 0; }
.pd-gen-param {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.pd-gen-param:last-child { border-bottom: 0; }
.pd-gen-param label { font-size: var(--text-sm); color: var(--text-body); min-width: 0; }
.pd-gen-guessed {
  font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--mo-warning); margin-left: var(--space-2);
  border: 1px solid currentColor; border-radius: var(--radius-sm);
  padding: 1px 5px; white-space: nowrap;
}
.pd-gen-param input {
  width: 88px; flex: none;
  font: inherit; font-size: var(--text-base);
  font-variant-numeric: tabular-nums; text-align: right;
  padding: 7px var(--space-2);
  color: var(--text-heading); background: var(--bg-page);
  border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
}
.pd-gen-param input:hover { border-color: var(--text-muted); }
.pd-gen-param input:focus-visible { outline: none; border-color: var(--accent); }

/* ---- deciding ---------------------------------------------------------- */

.pd-gen-decide {
  display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
}
.pd-gen-decide .pd-gen-accept { margin-right: auto; }
.pd-gen-failed { display: grid; gap: var(--space-4); justify-items: start; }
.pd-gen-failed-why { margin: 0; font-size: var(--text-sm); color: var(--mo-warning); }

@media (max-width: 520px) {
  .pd-gen-decide { display: grid; }
  .pd-gen-decide .pd-gen-accept { margin-right: 0; }
  .pd-gen-param { flex-wrap: wrap; }
}
@media (prefers-reduced-motion: reduce) {
  .pd-gen-spin, .pd-gen-joke, .pd-gen-think { animation: none; }
  .pd-gen-open-chev { transition: none; }
}

/* ---- somebody already made this --------------------------------------- */
/* Sits between the wait and the answer, and stays visible beside the drawn
   part: the free thing that already exists deserves to be seen before the
   person commits to the one that cost money to draw. Accent-ruled like the
   assumptions list rather than boxed, so it reads as part of the panel. */
.pd-gen-found {
  display: grid;
  gap: var(--space-2);
  padding-left: var(--space-4);
  border-left: 2px solid var(--mo-accent-soft);
}
.pd-gen-hits {
  margin: 0; padding: 0; list-style: none;
  display: grid; gap: var(--space-3);
}
.pd-gen-hits li { display: grid; gap: 1px; min-width: 0; }
.pd-gen-hits a {
  font-size: var(--text-sm);
  color: var(--accent);
  text-decoration: none;
  overflow-wrap: anywhere;
}
.pd-gen-hits a:hover { text-decoration: underline; text-underline-offset: 3px; }
.pd-gen-hits a:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pd-gen-hit-site {
  font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-muted);
}
.pd-gen-hit-why { font-size: var(--text-xs); color: var(--text-body); line-height: 1.5; }
