/* The component kit (design system SYS-9a), moved verbatim from builder.css; see the components
 * registry for what each block styles and the SYS-9a record for the two blocks not moved yet. */

button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[contenteditable='true']:focus-visible,
[tabindex]:focus-visible {
  outline: var(--pb-focus-ring);
  outline-offset: var(--pb-focus-ring-offset);
  box-shadow: var(--pb-focus-ring-glow);
}

/* Subtler focus for compact controls — no glow ring */
.pb-btn-icon:focus-visible,
.pb-toggle:focus-visible,
/* The switch's input is 0×0 and transparent, so its ring is drawn on the track a person sees
   (SYS-8 F-3). */
.pb-toggle:focus-visible + .pb-toggle-slider,
.pb-switch-input:focus-visible ~ .pb-switch-track,
.pb-color-input:focus-visible,
[contenteditable='true']:focus-visible,
.pb-nudge-btn:focus-visible,
.pb-sl-grip:focus-visible,
.pb-category-tab:focus-visible,
.pb-page-tab:focus-visible {
  outline: 2px solid var(--pb-accent);
  outline-offset: 1px;
  box-shadow: none;
}

.pb-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Template selector modal */
.pb-modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--pb-overlay-bg);
  z-index: var(--pb-z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  animation: pb-fade-in var(--pb-transition);
}

.pb-modal-overlay.pb-modal-overlay-closing {
  animation: pb-fade-out var(--pb-transition) forwards;
  pointer-events: none;
}

@keyframes pb-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes pb-fade-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

/* ── The card's own entrance and exit ────────────────────────────────────────
   Only the OVERLAY animated before this: the scrim faded and the card was
   simply there, at full size, on the first frame. That is what makes a dialog
   feel like it was pasted on rather than opened — and it was true of all 45
   modals, because nothing in the kit ever moved the card. The phone sheet had
   `pb-sheet-rise`; the desktop card had nothing.

   A short rise with a slight scale, on `--pb-ease-entrance` — the same curve
   the app's own boot uses, so a dialog opens the way the product opens. The
   exit is faster and does not scale back down: a close should feel like it is
   getting out of the way, and a symmetric reverse reads as slow.

   `transform` and `opacity` only — both composited, so this costs no layout on
   a card that may hold hundreds of cards. */
@keyframes pb-modal-rise {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes pb-modal-sink {
  from {
    opacity: 1;
    transform: none;
  }
  to {
    opacity: 0;
    transform: translateY(4px);
  }
}

/* `backwards`, NOT `both`.
   ────────────────────────────────────────────────────────────────────────────
   `backwards` applies the first keyframe before the animation starts, which is
   what stops a one-frame flash of the card at full size. `both` would ALSO fill
   the last keyframe forever after — and a filled `transform`, even one whose
   value is `none`, keeps the property animated on the card.

   That matters because a transformed ancestor becomes the containing block for
   `position: fixed` descendants, and `.pb-tplx-lightbox` (palette.css) is
   exactly that: a `position: fixed; inset: 0` section-thumbnail lightbox that
   lives INSIDE this card. Anchored to the card it would fill the dialog instead
   of the viewport. With `backwards` the transform exists only for the 200ms the
   animation is actually running, during which no lightbox can be opened. */
.pb-modal-overlay > .pb-modal {
  animation: pb-modal-rise var(--pb-transition-slow) var(--pb-ease-entrance) backwards;
}

.pb-modal-overlay.pb-modal-overlay-closing > .pb-modal {
  animation: pb-modal-sink var(--pb-modal-exit) ease forwards;
}

/* The card must not move for anyone who asked it not to. The overlay's own
   fade stays: it is an opacity change, not motion, and removing it would make
   a dialog appear instantly over the canvas with no transition at all. */
@media (prefers-reduced-motion: reduce) {
  .pb-modal-overlay > .pb-modal,
  .pb-modal-overlay.pb-modal-overlay-closing > .pb-modal {
    animation: none;
  }
}

.pb-modal {
  background: var(--pb-modal-surface);
  border: 1px solid var(--pb-modal-border);
  border-radius: var(--pb-modal-radius);
  /* min(), so the card always keeps a 16px gutter no matter which term is
     smaller. 90vw alone leaves 19px at 390px but only 16px at 320px, and the
     tier rules below can push max-width past what the viewport actually has. */
  width: min(90vw, calc(100% - 32px));
  /* Mobile P10 — the escape hatch, moved from `element.style` to a custom
     property. Nine call sites set `modal.style.maxWidth` inline, and an inline
     style outranks EVERY stylesheet rule: the phone sheet rule P11 is about to
     add could never have reached any of them. A custom property loses to a
     media query the same way a stylesheet declaration does, which is exactly
     what makes it safe. The 1000px fallback must match everywhere it appears —
     the CK-5 divergent-fallback gate checks that. */
  max-width: var(--pb-modal-max-w, 1000px);
  /* dvh, not vh: with Safari's URL bar expanded, 85vh overflows the visible
     area and the modal footer — where the confirm button lives — is unreachable. */
  max-height: 85dvh;
  display: flex;
  flex-direction: column;
  box-shadow: var(--pb-modal-shadow);
}

/* ── A card whose height is NOT a function of its content ────────────────────
   `.pb-modal` sets only a max-height, so a card is as tall as whatever is in
   it. That is right for a confirm dialog and wrong for anything whose content
   CHANGES while it is open — a tab strip whose panels differ in length, a list
   that shrinks as you filter it, a skeleton that resolves into fewer rows than
   it drew. Those jump, and the thing you were reading moves out from under the
   pointer.

   Opt in with `className: 'pb-modal-steady'`. ONE height, deliberately: a
   per-surface `--pb-modal-steady-h` would be a scoped token fork, which the
   design-tokens ledger counts and shrinks only — and a hook nothing is allowed
   to set is a fiction, not an extension point. A surface that genuinely needs
   another tier can override `height` under its own class.

   Only for a body that can ABSORB the slack — a designed scroller that fills.
   On a form-shaped body a fixed height is just dead space, so this is opt-in
   and not the default. Dropped at the phone band, where the card is a bottom
   sheet already sized by the viewport. */
.pb-modal-steady {
  height: min(720px, 85dvh);
}

/* A header with neither a title nor a close button is an empty padded bar with
   a bottom border. The command palette is the first caller to pass both
   title:null and showClose:false, but the defect is the kit's, so the fix is. */
.pb-modal-header:empty {
  display: none;
}

.pb-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--pb-modal-header-pad);
  border-bottom: 1px solid var(--pb-modal-border);
}

.pb-modal-header h2 {
  font-size: var(--pb-modal-title-font);
  font-weight: var(--pb-weight-semibold);
  color: var(--pb-text-bright);
}

/* Context line (PP-0, createModal `context`): the dialog's target under its
   title — "between Hero and Services". The wrapper exists ONLY when a context
   is set, so the header's flex geometry is untouched for every other dialog. */
.pb-modal-heading {
  display: flex;
  flex-direction: column;
  gap: var(--pb-space-1);
  min-width: 0;
}

.pb-modal-context {
  display: flex;
  align-items: center;
  gap: var(--pb-space-3);
  min-width: 0;
  color: var(--pb-text-dim);
  font-size: var(--pb-font-sm);
}

.pb-modal-context-thumb {
  width: 40px;
  height: 26px;
  flex: 0 0 auto;
  object-fit: cover;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm);
}

.pb-modal-context-text {
  margin: 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pb-modal-close {
  /* Mobile P6 — the register, not a literal: 28px on desktop (was 30, −2px) and
     44px on a coarse pointer. The button used to render a bare '×' character
     sized by font-size, which no glyph ramp could ever reach; it now carries a
     registry glyph and `padding` is gone because the glyph sizes itself.
     `margin: -8px` stays: it is a header-alignment nudge, letting the button
     paint larger than the layout space it occupies. */
  width: var(--pb-icon-btn);
  height: var(--pb-icon-btn);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--pb-text-dim);
  margin: var(--pb-modal-close-offset);
  transition:
    background var(--pb-transition),
    color var(--pb-transition);
}

.pb-modal-close:hover {
  background: var(--pb-bg-hover);
  color: var(--pb-text-bright);
}

.pb-modal-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--pb-modal-body-pad);
}

/* ── Split body (SW-1f) — content pane + persistent rail ──
   MUST stay after `.pb-modal-body`: both are single-class selectors, so the
   `padding: 0` that unpads the flex container only wins by source order. The
   rule used to live in palette.css, which is later in the cascade and won that
   way; it is the kit's now and pays for the move by position. */
.pb-modal-body-split {
  display: flex;
  padding: 0;
  overflow: hidden;
  /* `flex: 1 1 auto`, NOT `1 1 0`, AND NO `min-height: 0` HERE.
     ─────────────────────────────────────────────────────────────────────────
     `.pb-modal` has `max-height: 85dvh` and no `height`, so a column flex item
     with `flex-basis: 0` contributes nothing to the container's content height
     and `min-height: 0` removes the automatic min-content floor that would
     otherwise save it. The result is a split body EXACTLY 0px tall with the
     footer painted over the content — which is what happened, and which every
     jsdom test passed straight through because jsdom does not lay out.

     This rule came from palette.css, where it worked because
     `.pb-modal:has(…)` also set `height: 85vh` — an explicit height gives the
     flex container free space to distribute. SW-1 re-scoped that block to
     `.pb-asm-sidebar` (correctly: it is add-section's glass-panel repaint, not
     the kit's), which detached the height from the split for every NEW
     consumer. `auto` sizes to content and then shrinks under max-height, which
     is what the plain `.pb-modal-body` did all along.

     The `min-height: 0` that the SCROLLING children need is on them. */
  flex: 1 1 auto;
}

/* The padded scroller the caller appends to. `.pb-modal-body` cannot be both a
   padded scroller and a flex row, which is the whole reason three surfaces
   hand-rolled this rather than passing an option. */
.pb-modal-body-main {
  flex: 1 1 0;
  min-width: 0;
  /* On the CHILD, where it belongs: this is the pane that scrolls, and a flex
     item cannot shrink below its content without it. */
  min-height: 0;
  overflow-y: auto;
  padding: var(--pb-modal-body-pad);
}

.pb-modal-rail {
  /* Width comes from `--pb-modal-rail-w`, written by createModal from
     `rail.width`. A custom property rather than an inline `width` so the phone
     rule below can still collapse it without !important. */
  flex: 0 0 auto;
  width: var(--pb-modal-rail-w, 320px);
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  padding: var(--pb-modal-body-pad);
  border-left: 1px solid var(--pb-modal-border);
  background: var(--pb-surface-2);
}

/* A rail placed at the start borders on its trailing edge instead. */
.pb-modal-body-split > .pb-modal-rail:first-child {
  border-left: none;
  border-right: 1px solid var(--pb-modal-border);
}

/* Shared modal footer — the createModal() footer surface. Modal-specific
   footer classes layer their own specifics on top (e.g. justify-content). */
.pb-modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--pb-space-4);
  padding: var(--pb-modal-footer-pad);
  border-top: 1px solid var(--pb-modal-border);
  /* SW-5: the footer is the one part of a modal that must never be squeezed —
     it holds the confirm. Without this it is `flex-shrink: 1` beside a
     `flex: 1 1 0` body, so a tall body compresses it and the buttons overlap
     the content above them. Found by the geometry harness, which could not
     hover a field because the footer had collapsed onto it. */
  flex: 0 0 auto;
}

.pb-modal-footer:empty {
  display: none;
}

/* ── Sheet gesture layer (Mobile P12) ────────────────────────────────────
   Only the drag affordance and the touch-action contract live here; the sheet
   GEOMETRY is the P11 block above and works with or without this. */

.pb-sheet-grabber {
  display: none;
}

@media (max-width: 640px) {
  .pb-modal-overlay .pb-sheet .pb-sheet-grabber {
    display: flex;
    align-items: center;
    justify-content: center;
    /* A generous strip, not a hairline: this is the one target whose entire
       job is to be grabbed, and the visible bar inside it is only 4px tall. */
    height: 22px;
    flex: none;
    /* The browser must not claim this gesture for scrolling — the whole point
       is that we handle it. */
    touch-action: none;
    cursor: grab;
  }

  .pb-modal-overlay .pb-sheet .pb-sheet-grabber > span {
    width: 36px;
    height: 4px;
    border-radius: var(--pb-radius-pill);
    background: var(--pb-border);
  }

  /* The header is a secondary drag origin, so it gives up its pan gestures
     too. sheet.js still declines the drag when the press lands on a button. */
  .pb-modal-overlay .pb-sheet .pb-sheet-header {
    touch-action: none;
  }

  /* The body keeps VERTICAL panning — it is a scroller first and a drag origin
     only at scrollTop 0. `contain` stops a flick past the end from scrolling
     whatever is behind the sheet. */
  .pb-modal-overlay .pb-sheet .pb-sheet-body {
    touch-action: pan-y;
    overscroll-behavior: contain;
  }

  /* While dragging: no transition, or the card lags the finger. The rise-in
     animation is also cleared, since a drag can start before it finishes. */
  .pb-modal-overlay .pb-sheet.pb-sheet-dragging {
    transition: none;
    animation: none;
    cursor: grabbing;
    will-change: transform;
  }

  /* On release: one transition, removed by the next drag. Skipped entirely
     under prefers-reduced-motion (below), where the snap is instant. */
  .pb-modal-overlay .pb-sheet.pb-sheet-settling {
    transition: transform var(--pb-transition-slow) var(--pb-ease-entrance);
  }
}

@media (prefers-reduced-motion: reduce) {
  .pb-modal-overlay .pb-sheet.pb-sheet-settling {
    transition: none;
  }
}

/* ── Non-modal detented sheet (Mobile P13) ───────────────────────────────
   NO overlay, and that IS the feature: with no scrim there is nothing to
   intercept pointer events, so the canvas above stays visible AND live while
   the sheet is open. That is the whole difference from a modal sheet, and it
   is why `full` is never 100% — see SHEET_FULL_GAP_PX in ui/sheet.js. */

.pb-sheet--nonmodal {
  position: fixed;
  left: 0;
  right: 0;
  /* CMD-7: above the phone's bottom bar, which is `position: fixed` at 0. A
     sheet whose bottom edge is behind the bar hides its own last row — and the
     last row is the one nearest the thumb. 0 on desktop, where the bar is in
     flow and occludes nothing. */
  bottom: var(--pb-command-bottom-height-actual);
  /* Below --pb-z-modal (455), so a real dialog still paints over it — the
     sheet is inerted at the same time by syncModalStack(). */
  z-index: var(--pb-z-docked-panel);
  height: var(--pb-sheet-h, 50dvh);
  max-height: calc(
    100dvh - var(--pb-keyboard-inset, 0px) - var(--pb-command-bottom-height-actual)
  );
  display: flex;
  flex-direction: column;
  background: var(--pb-modal-surface);
  border: 1px solid var(--pb-modal-border);
  border-bottom: 0;
  border-radius: var(--pb-modal-radius) var(--pb-modal-radius) 0 0;
  box-shadow: var(--pb-shadow-panel);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  overflow: hidden;
}

/* The grabber is the primary affordance here — unlike the modal sheet, the
   detent ladder makes it the only way to resize — so it shows at every width. */
.pb-sheet--nonmodal .pb-sheet-grabber {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 22px;
  flex: none;
  touch-action: none;
  cursor: grab;
}

.pb-sheet--nonmodal .pb-sheet-grabber > span {
  width: 36px;
  height: 4px;
  border-radius: var(--pb-radius-pill);
  background: var(--pb-border);
}

.pb-sheet--nonmodal .pb-sheet-header {
  touch-action: none;
  flex: none;
}

.pb-sheet--nonmodal .pb-sheet-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  touch-action: pan-y;
  overscroll-behavior: contain;
}

.pb-sheet--nonmodal.pb-sheet-dragging {
  transition: none;
  cursor: grabbing;
  will-change: transform;
}

.pb-sheet--nonmodal.pb-sheet-settling {
  transition: transform var(--pb-transition-slow) var(--pb-ease-entrance);
}

@media (prefers-reduced-motion: reduce) {
  .pb-sheet--nonmodal.pb-sheet-settling {
    transition: none;
  }
}

@keyframes pb-sheet-rise {
  from {
    transform: translateY(100%);
  }
  to {
    transform: translateY(0);
  }
}

@media (prefers-reduced-motion: reduce) {
  /* The sheet still appears; it just does not travel. Two classes again, to
     beat the tier rules for the same reason the block above needs them. */
  .pb-modal-overlay .pb-modal {
    animation: none;
  }
}

/* Common button styles */
.pb-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--pb-space-3);
  padding: var(--pb-space-3) var(--pb-space-5);
  border-radius: var(--pb-radius);
  font-size: var(--pb-font-base);
  font-weight: var(--pb-weight-medium);
  transition:
    background var(--pb-transition),
    color var(--pb-transition);
  white-space: nowrap;
}

/* `display: inline-flex` is an author rule and `[hidden] { display: none }` is a
   UA rule, so the author one wins on every .pb-btn ever toggled via the `hidden`
   attribute — the button kept rendering. Restated here so each call site does
   not have to discover it separately. */
.pb-btn[hidden] {
  display: none;
}

.pb-btn-pill,
.pb-pill {
  border-radius: var(--pb-radius-pill);
}

.pb-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--pb-space-2);
  padding: var(--pb-space-1) var(--pb-space-3);
  background: var(--pb-surface-2);
  color: var(--pb-text);
  font-size: var(--pb-font-sm);
  font-weight: var(--pb-weight-medium);
}

.pb-pill[hidden] {
  display: none;
}

/* Pill tones (consolidation C-3): a status colour from the existing tint tokens. */
.pb-pill.pb-tone-danger {
  background: var(--pb-danger-bg);
  color: var(--pb-danger-text);
}

.pb-pill.pb-tone-accent {
  background: var(--pb-accent-tint);
  color: var(--pb-accent);
}

.pb-pill.pb-tone-warning {
  background: var(--pb-warning-bg);
  color: var(--pb-warning-text);
}

/* Consolidation C-22: two tones from existing tokens, the count form and the tag shape. */
.pb-pill.pb-tone-success {
  background: var(--pb-success-bg);
  color: var(--pb-success-text);
}

.pb-pill.pb-tone-solid {
  background: var(--pb-accent);
  color: var(--pb-on-accent);
}

.pb-pill.pb-form-count {
  justify-content: center;
  min-width: 20px;
  height: 18px;
  padding: 0 var(--pb-space-2);
  font-weight: var(--pb-weight-semibold);
  line-height: var(--pb-leading-none);
  font-variant-numeric: tabular-nums;
}

.pb-pill.pb-shape-tag {
  border-radius: var(--pb-radius-sm);
}

.pb-btn-primary {
  background: var(--pb-accent);
  color: var(--pb-on-accent);
}

.pb-btn-primary:hover {
  background: var(--pb-accent-hover);
}

.pb-btn-ghost {
  color: var(--pb-text);
}

.pb-btn-ghost:hover {
  background: var(--pb-surface-hover);
  color: var(--pb-text-bright);
}

/* Navigation, independent toggles and exclusive choices share one treatment — icon
   buttons included (consolidation C-2: a toggle is a pressed button, solid accent). */
.pb-btn[aria-current='page'],
.pb-btn[aria-pressed='true'],
.pb-btn[aria-checked='true'],
.pb-btn-icon[aria-pressed='true'] {
  background: var(--pb-accent);
  color: var(--pb-on-accent);
}

.pb-btn-danger {
  color: var(--pb-danger-text);
}

.pb-btn-danger:hover {
  background: var(--pb-danger-muted);
}

.pb-btn[aria-current='page']:hover,
.pb-btn[aria-pressed='true']:hover,
.pb-btn[aria-checked='true']:hover,
.pb-btn-icon[aria-pressed='true']:hover {
  background: var(--pb-accent-hover);
  color: var(--pb-on-accent);
}

@media (forced-colors: active) {
  .pb-btn[aria-current='page'],
  .pb-btn[aria-pressed='true'],
  .pb-btn[aria-checked='true'],
  .pb-btn-icon[aria-pressed='true'],
  .pb-btn[aria-current='page']:hover,
  .pb-btn[aria-pressed='true']:hover,
  .pb-btn[aria-checked='true']:hover,
  .pb-btn-icon[aria-pressed='true']:hover {
    background: Highlight;
    color: HighlightText;
    outline: 2px solid Highlight;
    outline-offset: 2px;
    forced-color-adjust: none;
  }
}

.pb-btn-icon {
  width: 32px;
  height: 32px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--pb-radius);
  color: var(--pb-text);
  transition:
    background var(--pb-transition),
    color var(--pb-transition);
}

.pb-btn-icon:hover {
  background: var(--pb-surface-hover);
  color: var(--pb-text-bright);
}

/* The compact icon button (consolidation C-1): a dense panel row such as Find & Replace.
   Width and height are sizes, not a spacing scale (rules-spec §4, not checked on purpose). */
.pb-btn-icon.pb-btn-icon-sm {
  width: 24px;
  height: 24px;
}

.pb-btn-icon.pb-active {
  background: var(--pb-accent);
  color: var(--pb-on-accent);
}

/* ── Selection checkbox (component kit) ──────────────────────────────────
   The kit's createCheckbox() has emitted `.pb-checkbox-input` since 16A1, but
   NOTHING styled it — every multi-select surface in the builder was painting a
   raw system widget, at whatever size and colour the OS felt like. A 13px
   platform box has no contrast guarantee against a thumbnail, no accent tie to
   the product, and no shared focus ring.

   Drawn on the INPUT itself rather than on a sibling box element: string-
   rendered call sites (the export page lists, the AI-rewrite rows) adopt it by
   adding one class, with no DOM restructuring. That also keeps it clear of the
   repo rule against pseudo-elements on replaced elements — `appearance: none`
   plus a background-image tick needs neither a ::before nor an extra <span>.

   The tick is a background-image so it can be recoloured per state by swapping
   the data URI. Size is a plain width/height a caller may restate (the idiom
   `.pb-btn-sm` already uses) — NOT a scoped `--pb-*` fork, which is debt the
   design-tokens ledger counts and was right to reject. */
.pb-checkbox {
  display: inline-flex;
  align-items: center;
  gap: var(--pb-space-3);
  cursor: pointer;
}

.pb-checkbox-input {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm);
  background: var(--pb-surface);
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
  cursor: pointer;
  transition:
    background-color var(--pb-transition),
    border-color var(--pb-transition);
}

.pb-checkbox-input:checked,
.pb-checkbox-input:indeterminate {
  background-color: var(--pb-accent);
  border-color: var(--pb-accent);
}

/* %23fff rather than #fff — an unescaped '#' terminates the data URI and the
   tick silently never paints. */
.pb-checkbox-input:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E");
}

.pb-checkbox-input:indeterminate {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.5' stroke-linecap='round'%3E%3Cpath d='M6 12h12'/%3E%3C/svg%3E");
}

.pb-checkbox-input:hover:not(:disabled):not(:checked):not(:indeterminate) {
  border-color: var(--pb-accent-subtle);
}

.pb-checkbox-input:focus-visible {
  outline: 2px solid var(--pb-accent);
  outline-offset: 2px;
}

.pb-checkbox-input:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}

/* On-imagery variant. A card poster is an arbitrary photograph, so the box
   cannot borrow the surface for contrast the way a panel row can — it carries
   its own: a white rim and a dark scrim beneath, plus a drop shadow to hold the
   edge against a light image. Unchecked and checked are the same geometry, so
   ticking recolours rather than resizing. */
.pb-checkbox-on-media {
  width: 22px;
  height: 22px;
  border-width: 2px;
  border-color: var(--pb-checkbox-media-border);
  border-radius: var(--pb-radius-sm);
  background-color: var(--pb-checkbox-media-bg);
  box-shadow: var(--pb-checkbox-media-shadow);
}

/* Forced colors flattens author paint and `appearance: none` would leave an
   empty rectangle with no state at all. Hand the box back to the platform,
   which draws a real checkmark in system ink. */
@media (forced-colors: active) {
  .pb-checkbox-input {
    appearance: auto;
    -webkit-appearance: auto;
    background-image: none;
  }
}

/* ── Size modifier ── */
.pb-btn-sm {
  padding: var(--pb-space-2) var(--pb-space-4);
  font-size: var(--pb-font-sm);
  font-weight: var(--pb-weight-regular);
  white-space: nowrap;
}

/* ── Outline modifier (bordered button on elevated surface) ── */
.pb-btn-outline {
  border: 1px solid var(--pb-border);
  background: var(--pb-bg-elevated);
  color: var(--pb-text-bright);
}

/* ── Segmented control (MK-1) ────────────────────────────────────────────
   The neutral half of what `.pb-device-segmented` has been carrying alone.
   Values are that rule's, unchanged, so the switcher's computed geometry does
   not move — the command-bar geometry gate is the proof, not this comment.

   What is NOT here is as deliberate as what is: no width, no height. A generic
   segment sizes to its content; the 34x32 box and the hover-expanding label
   belong to the DEVICE switcher and stay in toolbar.css, which loads later and
   therefore wins the equal-specificity tie. `.pb-history-group` keeps its own
   copy of these declarations there too — it borrows the look but is a pair of
   independent buttons, not an exclusive choice, and collapsing the two would
   make undo/redo claim a radiogroup's semantics. */
.pb-seg {
  display: inline-flex;
  align-items: center;
  gap: 0;
  padding: var(--pb-space-1);
  border-radius: var(--pb-radius-pill);
  background: var(--pb-segmented-track);
  border: 1px solid var(--pb-seg-border);
}

/* The same UA-vs-author defeat `.pb-btn[hidden]` documents above: `.pb-seg` sets
   `display: inline-flex`, which is an author rule and beats the UA's
   `[hidden] { display: none }`, so a segmented control toggled by the `hidden`
   attribute went on rendering. Found 2026-09-07 when the variant picker's detail
   stage stood its scope toggle down and the toggle stayed on screen. */
.pb-seg[hidden] {
  display: none;
}

.pb-seg-option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--pb-space-2);
  padding: var(--pb-space-2) var(--pb-space-5);
  border-radius: var(--pb-radius-pill);
  color: var(--pb-text-dim);
  white-space: nowrap;
  transition:
    background var(--pb-transition-slow),
    color var(--pb-transition);
}

.pb-seg-option.pb-active {
  background: var(--pb-segmented-thumb);
  color: var(--pb-text-bright);
  box-shadow: var(--pb-seg-active-shadow);
}

/* The COMPACT register — a segmented control on a dialog's HEADER row, beside
   a title, rather than in a form. Add Section and the variant picker both ask
   "CodeStitch, or every source?" in that corner, and each had a byte-identical
   copy of these declarations in its own feature sheet.

   `white-space: nowrap` is deliberately NOT repeated here: `.pb-seg-option`
   above already sets it, and both copies restated it for no effect. */
.pb-seg-compact .pb-seg-option {
  min-height: var(--pb-seg-compact-height);
  padding: 0 var(--pb-space-4);
  font-size: var(--pb-font-sm);
}

.pb-btn-outline:hover {
  background: var(--pb-bg-hover);
}

/* The link variant (consolidation C-7): the toast's action, promoted, its padding on the
   scale's nearest steps. The toast moves onto it at the burn-down. */
.pb-btn-link {
  padding: var(--pb-space-2) var(--pb-space-4);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm);
  background: transparent;
  color: var(--pb-accent);
  font-size: var(--pb-font-sm);
  font-weight: var(--pb-weight-semibold);
}

.pb-btn-link:hover {
  background: var(--pb-bg-hover);
}

/* ── Tabs (consolidation C-13) ─────────────────────────────────────────────
   createTabs. Underline is the left panel's look (.pb-ptz-tab); segmented wears the segmented
   control's classes; vertical is the settings rail, on the scale. */
.pb-tabs {
  display: flex;
}

.pb-tabs-underline .pb-tab {
  flex: 1 1 0;
  min-width: 0;
  min-height: max(40px, var(--pb-control-height));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--pb-space-4);
  padding: 0 var(--pb-space-4);
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--pb-text-dim);
  font-size: var(--pb-font-lg);
  font-weight: var(--pb-weight-medium);
  white-space: nowrap;
  cursor: pointer;
  transition:
    color var(--pb-transition),
    background var(--pb-transition),
    border-color var(--pb-transition);
}

.pb-tabs-underline .pb-tab:hover {
  background: var(--pb-bg-hover);
  color: var(--pb-text);
}

.pb-tabs-underline .pb-tab:focus-visible {
  outline: 2px solid var(--pb-accent);
  outline-offset: -2px;
  box-shadow: none;
}

.pb-tabs-underline .pb-tab[aria-selected='true'] {
  border-bottom-color: var(--pb-accent);
  background: var(--pb-accent-muted);
  color: var(--pb-accent);
  font-weight: var(--pb-weight-semibold);
}

.pb-tab-icon {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
}

.pb-tab-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pb-tabs-vertical {
  flex-direction: column;
  gap: var(--pb-space-1);
}

.pb-tabs-vertical .pb-tab {
  flex: none;
  min-height: 0;
  justify-content: flex-start;
  gap: var(--pb-space-4h);
  padding: var(--pb-space-4) var(--pb-space-5);
  border: 0;
  border-radius: var(--pb-radius-md);
  color: var(--pb-text);
  font-size: var(--pb-font-md);
  text-align: left;
}

.pb-tabs-vertical .pb-tab[aria-selected='true'] {
  background: var(--pb-bg-hover);
  color: var(--pb-text-bright);
}

.pb-tabs-vertical .pb-tab svg {
  color: var(--pb-text-dim);
}

.pb-tabs-vertical .pb-tab[aria-selected='true'] svg {
  color: var(--pb-accent);
}

.pb-tab-panel[hidden] {
  display: none;
}

/* ── Switch (consolidation C-14) ───────────────────────────────────────────
   createSwitch, in the settings switch's look; its ring is in the compact group above. */
.pb-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--pb-space-4);
  cursor: pointer;
}

.pb-switch-control {
  position: relative;
  width: 38px;
  height: 22px;
  flex: none;
}

.pb-switch-input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.pb-switch-track {
  position: absolute;
  inset: 0;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-pill);
  background: var(--pb-surface-3);
  pointer-events: none;
  transition:
    background var(--pb-transition-slow),
    border-color var(--pb-transition-slow);
}

.pb-switch-thumb {
  position: absolute;
  top: var(--pb-space-1h);
  left: var(--pb-space-1h);
  width: 16px;
  height: 16px;
  border-radius: var(--pb-radius-pill);
  background: var(--pb-switch-thumb);
  box-shadow: var(--pb-switch-thumb-shadow);
  pointer-events: none;
  transition: transform var(--pb-transition-spring);
}

.pb-switch-input:checked ~ .pb-switch-track {
  border-color: var(--pb-accent);
  background: var(--pb-accent);
}

.pb-switch-input:checked ~ .pb-switch-thumb {
  transform: translateX(16px);
}

.pb-switch:has(.pb-switch-input:disabled) {
  opacity: 0.45;
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  .pb-tabs-underline .pb-tab,
  .pb-switch-track,
  .pb-switch-thumb {
    transition: none;
  }
}

@media (forced-colors: active) {
  .pb-tabs-underline .pb-tab[aria-selected='true'] {
    background: Highlight;
    color: HighlightText;
  }

  .pb-switch-input:checked ~ .pb-switch-track {
    background: Highlight;
  }

  .pb-switch-thumb {
    background: CanvasText;
  }
}

/* Grayscale on icon buttons only — avoids hurting text readability */
.pb-btn-icon:disabled {
  filter: grayscale(1);
}

/* ── Component kit (components.js) ── */
/* Default icon wrapper for createButton({icon}); callers may override with a
   surface-specific icon class to keep existing CSS. */
.pb-btn-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ── createFilterSelect(): caption + pill trigger + listbox ──────────────────
   The shape the template picker's "Built by" control established, promoted out
   of palette.css so a second surface does not have to re-author it. The pill is
   sized and shaped as a chip on purpose: a filter row and a chip row are one
   control set, and two radii there read as two systems.

   `.pb-tpl-v2-filter-*` is still its own copy. It is a working control on a
   surface with recorded design decisions and no browser gate, so converting it
   is its own change — not a side effect of minting this. */
.pb-filter-select {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--pb-space-3);
  flex: 0 0 auto;
  min-width: 0;
}

.pb-filter-select-label {
  flex: 0 0 auto;
  color: var(--pb-text-dim);
  font-size: var(--pb-font-sm);
  white-space: nowrap;
}

.pb-filter-select-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--pb-space-2);
  max-width: 260px;
  min-width: 0;
  height: 30px;
  padding: 0 var(--pb-space-3) 0 var(--pb-space-4);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-pill);
  background: var(--pb-surface-2);
  color: var(--pb-text);
  font: inherit;
  font-size: var(--pb-font-sm);
  cursor: pointer;
  transition:
    border-color var(--pb-transition),
    background var(--pb-transition);
}

.pb-filter-select-trigger:hover:not(:disabled) {
  background: var(--pb-bg-hover);
}

.pb-filter-select-trigger[aria-expanded='true'] {
  border-color: var(--pb-accent);
  color: var(--pb-text-bright);
}

.pb-filter-select-trigger:focus-visible {
  outline: var(--pb-coll-ring-w) solid var(--pb-accent);
  outline-offset: 1px;
}

.pb-filter-select-trigger:disabled {
  color: var(--pb-text-dim);
  cursor: default;
}

.pb-filter-select-value {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pb-filter-select-caret {
  flex: 0 0 auto;
  color: var(--pb-text-dim);
  transition: transform var(--pb-transition);
}

.pb-filter-select-trigger[aria-expanded='true'] .pb-filter-select-caret {
  transform: rotate(180deg);
}

/* Right-aligned to the trigger: the control is pinned to the right edge of its
   row, so opening leftward would push the menu off-screen. */
.pb-filter-select-menu {
  position: absolute;
  top: calc(100% + var(--pb-space-2));
  right: 0;
  z-index: var(--pb-z-local-3);
  /* 240, matching the control this was promoted from. NOT 220: that is the
     collection grid's track width, and COLLECTION-3 censuses every 220px
     literal in the sheets — an unrelated menu carrying it reads as track drift. */
  min-width: 240px;
  max-width: 320px;
  max-height: 320px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--pb-space-2);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius);
  background: var(--pb-surface);
  box-shadow: var(--pb-filter-select-menu-shadow);
}

.pb-filter-select-menu[hidden] {
  display: none;
}

.pb-filter-select-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--pb-space-4);
  width: 100%;
  padding: var(--pb-space-2) var(--pb-space-3);
  border: 0;
  border-radius: var(--pb-radius-sm);
  background: transparent;
  color: var(--pb-text);
  font: inherit;
  font-size: var(--pb-font-sm);
  text-align: left;
  cursor: pointer;
}

.pb-filter-select-option:hover {
  background: var(--pb-bg-hover);
}

.pb-filter-select-option:focus-visible {
  outline: var(--pb-coll-ring-w) solid var(--pb-accent);
  outline-offset: -2px;
}

/* The same filled/on-accent pairing as an active chip — one selected-state
   idiom across the whole filter row. */
.pb-filter-select-option[aria-selected='true'] {
  background: var(--pb-accent);
  color: var(--pb-on-accent);
}

.pb-filter-select-option-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pb-filter-select-option-count {
  flex: 0 0 auto;
  color: var(--pb-text-dim);
  font-size: var(--pb-font-xs);
  font-variant-numeric: tabular-nums;
}

.pb-filter-select-option[aria-selected='true'] .pb-filter-select-option-count {
  color: inherit;
  opacity: 0.85;
}

/* Option groups (consolidation C-16). */
.pb-filter-select-group + .pb-filter-select-group {
  margin-top: var(--pb-space-2);
}

.pb-filter-select-group-label {
  padding: var(--pb-space-2) var(--pb-space-3);
  color: var(--pb-text-dim);
  font-size: var(--pb-font-xs);
  font-weight: var(--pb-weight-semibold);
}

/* Labeled input group from createLabeledInput() — visible label + control. */
.pb-field {
  display: flex;
  flex-direction: column;
  gap: var(--pb-space-2);
}
.pb-field-label {
  font-size: var(--pb-font-sm);
  font-weight: var(--pb-weight-medium);
  color: var(--pb-text-dim);
}
.pb-field-input {
  width: 100%;
  padding: var(--pb-space-3) var(--pb-space-4);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-md);
  background: var(--pb-surface);
  color: var(--pb-text);
  font-size: var(--pb-font-base);
  font-family: inherit;
}
.pb-field-input:focus-visible {
  outline: 2px solid var(--pb-accent);
  outline-offset: 1px;
}

/* Field sizes (consolidation C-18): sm is the compact field, lg the glass field. */
.pb-field-sm .pb-field-input {
  padding: var(--pb-space-2) var(--pb-space-3);
  font-size: var(--pb-font-sm);
}

.pb-field-lg .pb-field-input {
  min-height: 42px;
  padding: var(--pb-space-3) var(--pb-space-5h);
  border-radius: var(--pb-radius-lg);
  font-size: var(--pb-font-lg);
}

/* ── SW-1a: a field's three secondary states ──
   The tone classes sit on the WRAPPER so one class can move the control's
   border, the label and the hint together — which is why ten separate
   `-hint`/`-error` families each needed their own pair of rules and this needs
   one. `.pb-modal-hint` had no rule at all; those two hints in the publish
   dialog inherit this now. */
.pb-field-hint {
  margin: 0;
  font-size: var(--pb-font-xs);
  line-height: var(--pb-leading-normal);
  color: var(--pb-text-dim);
}

.pb-field.pb-field-warn .pb-field-hint {
  color: var(--pb-warning-text);
}
.pb-field.pb-field-warn .pb-field-input {
  border-color: var(--pb-warning-border);
}

.pb-field.pb-field-danger .pb-field-hint {
  color: var(--pb-danger-text);
}
.pb-field.pb-field-danger .pb-field-input {
  border-color: var(--pb-danger-border);
}

/* P9 — "you typed this" as a visible mark. A left rule rather than a colour
   change: the field may already be carrying a warn or danger tone, and two
   colour systems on one control cancel each other out. */
.pb-field.pb-field-edited {
  border-left: 2px solid var(--pb-accent);
  padding-left: var(--pb-space-3);
}

.pb-field.pb-field-edited .pb-field-label::after {
  content: ' · edited';
  color: var(--pb-accent);
  font-weight: var(--pb-weight-regular);
}

/* ── SW-3 / S3: the URL field ──
   A pill with a leading glyph and a live echo under it. The echo is the point:
   `yourclient.com` is not a URL and `https://yourclient.com/` is what will
   actually be fetched, so the field resolves it while you type instead of
   after you commit. */
.pb-url-field-iconed {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    'label label'
    'icon input'
    'hint hint';
  align-items: center;
  column-gap: 0;
}
.pb-url-field-iconed > .pb-field-label {
  grid-area: label;
}
.pb-url-field-iconed > .pb-url-field-icon {
  grid-area: icon;
  /* In the SAME grid cell region as the input's leading padding, so the glyph
     sits inside the pill rather than beside it. */
  margin-right: calc(var(--pb-space-4) * -1 - var(--pb-url-field-icon-box));
  z-index: var(--pb-z-local-1);
  padding-left: var(--pb-space-4);
  color: var(--pb-text-dim);
  pointer-events: none;
}
.pb-url-field-iconed > .pb-field-input {
  grid-area: input;
  padding-left: calc(var(--pb-space-4) * 2 + var(--pb-url-field-icon-inset));
}
.pb-url-field-iconed > .pb-field-hint {
  grid-area: hint;
}

.pb-url-field .pb-field-input {
  border-radius: var(--pb-radius-pill);
}

.pb-url-field:focus-within > .pb-url-field-icon {
  color: var(--pb-accent);
}

/* ── SW-1b: swatch + hex ── */
.pb-color-field .pb-field-input {
  /* The swatch sits beside the hex field, not under it. */
  flex: 1 1 auto;
  min-width: 0;
}

.pb-color-field {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    'label label'
    'input swatch'
    'hint hint';
  align-items: center;
  column-gap: var(--pb-space-3);
}
.pb-color-field > .pb-field-label {
  grid-area: label;
}
.pb-color-field > .pb-field-input {
  grid-area: input;
}
.pb-color-field > .pb-color-field-swatch {
  grid-area: swatch;
}
.pb-color-field > .pb-field-hint {
  grid-area: hint;
}

/* `layout: 'inline'` — swatch + hex on one line for a dense panel row. The
   label is VISUALLY hidden, not absent: an SVG with six fills otherwise gives a
   screen reader six identically-named colour controls. */
.pb-color-field-inline {
  grid-template-columns: auto 1fr;
  grid-template-areas:
    'swatch input'
    'hint hint';
  column-gap: var(--pb-space-2);
}

.pb-color-field-inline > .pb-field-label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.pb-color-field-inline > .pb-field-input {
  padding: var(--pb-space-2) var(--pb-space-3);
  font-size: var(--pb-font-sm);
}

.pb-color-field-swatch {
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-md);
  background: var(--pb-surface);
  cursor: pointer;
}

.pb-color-field-inline > .pb-color-field-swatch {
  width: 30px;
  height: 30px;
}

.pb-color-field-swatch:focus-visible {
  outline: 2px solid var(--pb-accent);
  outline-offset: 1px;
}

/* ── SW-1c: the step rail ── */
.pb-step-rail {
  display: flex;
  flex-wrap: wrap;
  gap: var(--pb-space-2) var(--pb-space-5);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: pb-step;
}

.pb-step-rail-step {
  counter-increment: pb-step;
  display: flex;
  align-items: center;
  gap: var(--pb-space-2);
  font-size: var(--pb-font-sm);
  color: var(--pb-text-dim);
}

.pb-step-rail-step::before {
  content: counter(pb-step);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid var(--pb-border);
  font-size: var(--pb-font-xs);
  line-height: var(--pb-leading-none);
}

.pb-step-rail-step.pb-step-rail-current {
  color: var(--pb-text);
  font-weight: var(--pb-weight-semibold);
}
.pb-step-rail-step.pb-step-rail-current::before {
  border-color: var(--pb-accent);
  background: var(--pb-accent);
  color: var(--pb-step-rail-current-fg);
}

.pb-step-rail-step.pb-step-rail-done::before {
  border-color: var(--pb-accent);
  color: var(--pb-accent);
}

/* A done step is a REAL control (P17). The bare button carries the label; the
   number stays on the <li> so an inert step and an activatable one line up. */
.pb-step-rail-btn {
  padding: 0;
  font: inherit;
  color: var(--pb-accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── SW-1d: the phase list ──
   NO new @keyframes. `is-active` is carried by data-status and painted with
   weight and colour; the attention-keyframe ledger is shrink-only. */
.pb-phase-list {
  display: flex;
  flex-direction: column;
  gap: var(--pb-space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.pb-phase-list-item {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--pb-space-1);
  padding-left: var(--pb-space-5);
  position: relative;
  font-size: var(--pb-font-sm);
  color: var(--pb-text-dim);
}

.pb-phase-list-item::before {
  content: '○';
  position: absolute;
  left: 0;
  top: 0;
  line-height: var(--pb-leading-normal);
}

.pb-phase-list-item[data-status='active'] {
  color: var(--pb-text);
  font-weight: var(--pb-weight-semibold);
}
.pb-phase-list-item[data-status='active']::before {
  content: '●';
  color: var(--pb-accent);
}

.pb-phase-list-item[data-status='done'] {
  color: var(--pb-text);
}
.pb-phase-list-item[data-status='done']::before {
  content: '✓';
  color: var(--pb-accent);
}

/* P4 — what the phase FOUND. This line is the entire reason the list replaced
   a bar: a bar reaches 100% and tells you nothing. */
.pb-phase-list-found {
  font-size: var(--pb-font-xs);
  font-weight: var(--pb-weight-regular);
  color: var(--pb-text-dim);
}

/* ── SW-1e: the danger block ──
   There was no danger-block CSS anywhere in the repo; eight prose-only confirms
   described the same risk in a sentence. */
.pb-danger-block {
  display: flex;
  flex-direction: column;
  gap: var(--pb-space-2);
  padding: var(--pb-space-4) var(--pb-space-5);
  border: 1px solid var(--pb-danger-border);
  border-radius: var(--pb-radius-md);
  background: var(--pb-danger-bg);
  color: var(--pb-text);
}

.pb-danger-title {
  font-size: var(--pb-font-base);
  font-weight: var(--pb-weight-semibold);
  color: var(--pb-danger-text);
}

.pb-danger-consequence {
  margin: 0;
  font-size: var(--pb-font-sm);
  line-height: var(--pb-leading-normal);
}

/* P14 — the safety net as a FACT of its own, not a clause. It is deliberately
   NOT danger-coloured: it is the reassuring half, and painting it red would
   make the whole block read as one undifferentiated warning. */
.pb-danger-safety {
  margin: 0;
  padding-top: var(--pb-space-2);
  border-top: 1px solid var(--pb-danger-border);
  font-size: var(--pb-font-sm);
  line-height: var(--pb-leading-normal);
  color: var(--pb-text-dim);
}

/* Loading skeletons */
@keyframes pb-skeleton-pulse {
  0%,
  100% {
    opacity: 0.4;
  }
  50% {
    opacity: 0.8;
  }
}

/* A thumbnail whose pixels have not arrived. Same pulse as `.pb-skeleton`, but
   applied to the real <img> so no extra element is needed and the reserved
   aspect box does not move when the picture appears. Stamped by
   shared/thumbnail-url.js, cleared by ui/feedback.js `installImageSkeletons`. */
[data-pb-thumb-loading] {
  background-color: var(--pb-surface-hover);
  animation: pb-skeleton-pulse var(--pb-transition-skeleton) ease-in-out infinite;
}

.pb-skeleton {
  background: var(--pb-surface-hover);
  border-radius: var(--pb-radius);
  animation: pb-skeleton-pulse var(--pb-transition-skeleton) ease-in-out infinite;
}

.pb-skeleton-row {
  display: flex;
  align-items: center;
  gap: var(--pb-space-4);
  padding: var(--pb-space-4) var(--pb-space-5);
}

/* `.pb-skeleton-thumb` / `-text` / `-text-short` lived here for the section-list
   skeleton, which was painted and overwritten in the same tick and has now been
   deleted. They were also the rules whose geometry never matched the real row
   (40x28 against `.pb-sl-thumb`'s 48x32) — audit item 4c, moot rather than
   fixed. `renderSkeletonRows` uses `.pb-skeleton-row` + `.pb-skeleton-line`. */

/* Row skeletons. `-revision` mirrors `.pb-rev-item`: same padding and bottom
   border, name/meta pair on the left, a Restore-sized block on the right — so
   the real rows swap in without changing the modal's scroll height. */
.pb-skeleton-row-revision {
  justify-content: space-between;
  padding: var(--pb-space-4);
  border-bottom: 1px solid var(--pb-border);
}

.pb-skeleton-row-revision:last-child {
  border-bottom: none;
}

.pb-skeleton-row-lines {
  display: flex;
  flex-direction: column;
  gap: var(--pb-space-1);
  flex: 1;
  min-width: 0;
}

.pb-skeleton-row-action {
  width: 64px;
  height: 24px;
  flex-shrink: 0;
}

/* ── Custom tooltip ── */
.pb-tooltip {
  position: fixed;
  z-index: var(--pb-z-toast);
  max-width: 240px;
  padding: var(--pb-space-2) var(--pb-space-4);
  background: var(--pb-surface);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius);
  box-shadow: var(--pb-shadow-dropdown);
  color: var(--pb-text-bright);
  font-size: var(--pb-font-sm);
  white-space: normal;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--pb-transition);
}

.pb-tooltip.pb-tooltip-show {
  opacity: 1;
}

.pb-tooltip-kbd {
  display: inline-block;
  margin-left: var(--pb-space-2);
  padding: 1px var(--pb-space-2);
  background: var(--pb-bg);
  border: 1px solid var(--pb-border);
  border-radius: var(--pb-radius-sm);
  font-size: var(--pb-font-xs);
  font-family: inherit;
  color: var(--pb-text-dim);
}

/* Skeleton cards (palette section grid + template catalog grid).
   The geometry deliberately mirrors the real card it stands in for: same thumb
   aspect ratio, same number of text lines. A skeleton that is systematically
   shorter than its replacement moves the page when it swaps out, which defeats
   the point of showing one. `animation-delay` is inherited by the children so
   the whole card pulses as one unit. */
.pb-skeleton-card {
  border-radius: var(--pb-radius);
  border: 1px solid var(--pb-border);
  overflow: hidden;
  animation-delay: inherit;
}

.pb-skeleton-card-thumb {
  width: 100%;
  border-radius: 0;
  animation-delay: inherit;
}

.pb-skeleton-card-section .pb-skeleton-card-thumb {
  aspect-ratio: 16 / 9;
}

.pb-skeleton-card-template .pb-skeleton-card-thumb {
  aspect-ratio: var(--pb-coll-poster-ratio);
}

/* Mirrors `.pb-image-picker-card img` (property-panel.css). */
/* MK-4a: reads the TOKEN, not a restated 3/2. The image skeleton used to
   mirror the picker's hand-rolled `.pb-image-picker-card img`; the picker's
   thumbnail is the kit's wide poster now, and layout-stability-tests compares
   the two declarations as strings — so a literal here and a var() there would
   be a false mismatch that hides a real one. */
.pb-skeleton-card-image .pb-skeleton-card-thumb {
  aspect-ratio: var(--pb-coll-poster-ratio-wide);
}

/* Mirrors `.pb-coll-poster` (collection.css) — the project card's poster box
   since CB-9. Same ratio the template card uses; the project card was once
   written `8 / 5`, which is the same number said a different way, and the
   parity test compares the declaration. */
.pb-skeleton-card-project .pb-skeleton-card-thumb {
  aspect-ratio: var(--pb-coll-poster-ratio);
  border-radius: var(--pb-radius-lg);
}

/* The real project card is a poster with no frame around it, so its skeleton
   must not be a bordered tile — otherwise the swap-in drops a 1px box that was
   never there. */
.pb-skeleton-card-project {
  border: 0;
  border-radius: var(--pb-radius-lg);
  overflow: visible;
}

.pb-skeleton-card-project .pb-skeleton-card-lines {
  padding: var(--pb-space-3) var(--pb-space-1) 0;
}

.pb-skeleton-card-lines {
  display: flex;
  flex-direction: column;
  gap: var(--pb-space-2);
  padding: var(--pb-space-3);
}

.pb-skeleton-line {
  height: 12px;
  border-radius: var(--pb-radius-sm);
  animation-delay: inherit;
}

.pb-skeleton-line-short {
  width: 60%;
}

/* Entrance / exit animations */
@keyframes pb-fade-slide-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes pb-fade-slide-out {
  from {
    opacity: 1;
    transform: translateX(0);
    /* pb-sys-exempt: sys/no-layout-animation from — row exit, CP-G */
    max-height: 200px;
  }
  40% {
    opacity: 0;
    transform: translateX(-20px);
    /* pb-sys-exempt: sys/no-layout-animation 40% — row exit, CP-G */
    max-height: 200px;
  }
  to {
    opacity: 0;
    transform: translateX(-20px);
    /* pb-sys-exempt: sys/no-layout-animation to — row exit, CP-G */
    max-height: 0;
    /* pb-sys-exempt: sys/no-layout-animation to — row exit, CP-G */
    padding-top: 0;
    /* pb-sys-exempt: sys/no-layout-animation to — row exit, CP-G */
    padding-bottom: 0;
    /* pb-sys-exempt: sys/no-layout-animation to — row exit, CP-G */
    margin-top: 0;
    /* pb-sys-exempt: sys/no-layout-animation to — row exit, CP-G */
    margin-bottom: 0;
    /* pb-sys-exempt: sys/no-layout-animation to — row exit, CP-G */
    border-width: 0;
  }
}

.pb-animate-in {
  animation: pb-fade-slide-in var(--pb-transition) both;
}

/* "It landed here" (PP-0, feedback.landed): one accent ring that fades over
   400ms on a freshly created or moved row/tile. The ONE landed keyframe for
   every list and grid — the canvas keeps pb-section-flash in iframe CSS. */
@keyframes pb-landed {
  from {
    box-shadow: var(--pb-landed-ring);
  }
  to {
    box-shadow: var(--pb-landed-ring-end);
  }
}

.pb-landed {
  animation: pb-landed var(--pb-landed-duration) ease-out both;
}

.pb-animate-out {
  animation: pb-fade-slide-out var(--pb-animate-out-duration) ease both;
  overflow: hidden;
  pointer-events: none;
}
