/* ContentBlocks — Admin (builder shell) styles */

/* Design tokens (colors, radii) — every rule below reads var(--cb-*) rather
   than a literal, so the chrome is restyled by overriding tokens. */
@import "tokens-5Q1-I-i.css";

/* Form widgets — scoped under .cb-shell, pairs with @ContentBlocks/form/cb_form_theme.html.twig. */
@import "forms-4l1CXu-.css";

/* ---- Launcher (form widget area) ----
 *
 * Lives on the host's edit page OUTSIDE the cb-shell scope, so the
 * button can't rely on the form-theme tokens defined in forms.css.
 * Keep these rules self-contained — same blue / radii / typography
 * as the rest of the builder so the launcher reads as part of the
 * same product. */

.cb-launcher {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.cb-launcher__button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.9rem;
  background: var(--cb-accent);
  color: var(--cb-on-solid);
  border: 1px solid var(--cb-accent);
  border-radius: var(--cb-radius-sm);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background-color 0.12s ease,
    border-color 0.12s ease,
    box-shadow 0.12s ease;
}
.cb-launcher__button:hover {
  background: var(--cb-accent-strong);
  border-color: var(--cb-accent-strong);
}
.cb-launcher__button:focus-visible {
  outline: 0;
  box-shadow: 0 0 0 3px rgba(var(--cb-accent-rgb), 0.25);
}

.cb-launcher__badge {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.45rem;
  background: var(--cb-badge-bg);
  color: var(--cb-badge-fg);
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.02em;
}

/* ---- Dialog wrapper (fullscreen) ---- */

.cb-builder-dialog {
  width: 100vw;
  max-width: 100vw;
  height: 100vh;
  max-height: 100vh;
  margin: 0;
  padding: 0;
  border: none;
  background: var(--cb-surface);
}

.cb-builder-dialog::backdrop {
  background: rgba(var(--cb-shadow-rgb), 0.6);
}

/* ---- Shell layout ---- */

.cb-shell {
  /* Positioning context for the floating undo snackbar. */
  position: relative;
  display: grid;
  grid-template-rows: 56px 1fr;
  height: 100%;
  width: 100%;
  background: var(--cb-surface);
  color: var(--cb-text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  overflow: hidden;
  /* Drives the sidebar grid column width below. JS updates this on
       resize / collapsed-state changes; descendants must NOT redeclare
       it or they'd shadow the live value back to the default. */
  --cb-sidebar-width: 340px;
  --cb-sidebar-collapsed-width: 32px;
}

.cb-shell__topbar {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  padding: 0 1rem;
  background: var(--cb-panel);
  border-bottom: 1px solid var(--cb-line);
  gap: 1rem;
  /* Make room for the progress bar that sits on the shell's top edge. */
  position: relative;
}

/* ---- Top-bar progress indicator ---- */

/* Thin indeterminate progress bar pinned to the very top of the shell, above
   the topbar. Visible only while the controller has an AJAX request in flight
   or the iframe is reloading; idle the bar is collapsed to zero height so it
   doesn't take any space. */
.cb-shell__progress {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 0;
  overflow: hidden;
  z-index: 10;
  pointer-events: none;
  transition: height 0.12s ease-out;
}
.cb-shell--loading .cb-shell__progress {
  height: 2px;
  background: rgba(var(--cb-accent-rgb), 0.15);
}
.cb-shell__progress::after {
  content: "";
  display: block;
  height: 100%;
  width: 30%;
  background: var(--cb-accent);
  animation: cb-shell-progress 1.1s ease-in-out infinite;
}
@keyframes cb-shell-progress {
  0% {
    transform: translateX(-100%);
  }
  50% {
    transform: translateX(150%);
  }
  100% {
    transform: translateX(350%);
  }
}

.cb-shell__topbar-left {
  justify-self: start;
  display: flex;
  gap: 0.5rem;
  align-items: center;
}
.cb-shell__topbar-right {
  justify-self: end;
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.cb-shell__close {
  background: transparent;
  border: 0;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.25rem 0.5rem;
  color: var(--cb-muted);
}
.cb-shell__close:hover {
  color: var(--cb-text);
}

/* ---- Topbar "Actions" menu ----
 *
 * Everything that acts on the area as a whole lives behind one button, so the
 * topbar carries only what an editor reaches for constantly (close, viewport,
 * discard, publish). A row of ghost buttons made the rare and the constant
 * look equally important, and every host action the package grew added another
 * one.
 */

.cb-shell__actions {
  position: relative;
}

.cb-shell__actions-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.375rem 0.75rem;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--cb-radius-sm);
  color: var(--cb-accent);
  font: inherit;
  font-size: 0.9rem;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background-color 0.12s ease,
    border-color 0.12s ease,
    color 0.12s ease;
}
.cb-shell__actions-toggle:hover,
.cb-shell__actions-toggle--open {
  background: rgba(var(--cb-accent-rgb), 0.08);
  border-color: rgba(var(--cb-accent-rgb), 0.25);
}
.cb-shell__actions-caret {
  font-size: 0.7rem;
  transition: transform 0.12s ease;
}
.cb-shell__actions-toggle--open .cb-shell__actions-caret {
  transform: rotate(180deg);
}

.cb-shell__actions-list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 40;
  min-width: 240px;
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--cb-panel);
  border: 1px solid var(--cb-line);
  border-radius: var(--cb-radius);
  box-shadow: 0 8px 24px rgba(var(--cb-shadow-rgb), 0.16);
}
.cb-shell__actions-list[hidden] {
  display: none;
}

.cb-shell__actions-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  background: transparent;
  border: 0;
  border-radius: var(--cb-radius-sm);
  color: var(--cb-text);
  font: inherit;
  font-size: 0.875rem;
  line-height: 1.2;
  text-align: left;
  cursor: pointer;
}
.cb-shell__actions-item:hover,
.cb-shell__actions-item:focus-visible {
  background: var(--cb-accent-bg);
  color: var(--cb-accent-darker);
}
/* In the topbar an icon was noise next to a labelled button; in a menu it is
   the fastest way to tell two rows apart, so here it shows. */
.cb-shell__actions-item .cb-shell__btn-icon {
  width: 1.15rem;
  flex: none;
  text-align: center;
  color: var(--cb-muted);
}
.cb-shell__actions-item:hover .cb-shell__btn-icon,
.cb-shell__actions-item:focus-visible .cb-shell__btn-icon {
  color: inherit;
}

/* ---- Modal pickers ----
 *
 * Replace / section library / import-export are centered dialogs over a
 * backdrop rather than panels pinned under the topbar. They were anchored to
 * the button that opened them, which put a 360px column in the top-left corner
 * of a 1600px screen and left the rest of the builder looking live while it
 * was not. Centering them says "this is modal"; the backdrop makes it true.
 */

.cb-modal-backdrop {
  position: absolute;
  inset: 0;
  z-index: 29;
  /* Tinted with the chrome accent rather than neutral black: the dimmed
     builder should still read as this tool, not as a generic overlay. */
  background: rgba(var(--cb-accent-rgb), 0.22);
  backdrop-filter: blur(1.5px);
}
.cb-modal-backdrop[hidden] {
  display: none;
}

.cb-replace-picker {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 420px;
  max-width: calc(100% - 32px);
  max-height: calc(100% - 96px);
  display: flex;
  flex-direction: column;
  background: var(--cb-panel);
  border: 1px solid var(--cb-line);
  border-radius: var(--cb-radius);
  box-shadow: 0 18px 48px rgba(var(--cb-shadow-rgb), 0.28);
  z-index: 30;
  overflow: hidden;
}
.cb-replace-picker[hidden] {
  display: none;
}

.cb-replace-picker__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--cb-panel-2);
  background: var(--cb-panel-3);
}
.cb-replace-picker__title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--cb-text);
}
.cb-replace-picker__close {
  background: transparent;
  border: 0;
  font-size: 1.25rem;
  line-height: 1;
  padding: 0.15rem 0.35rem;
  cursor: pointer;
  color: var(--cb-muted);
}
.cb-replace-picker__close:hover {
  color: var(--cb-text);
}

.cb-replace-picker__body {
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  overflow-y: auto;
}

/* ---- Section library, in the empty sidebar ----
 *
 * Not a modal: with nothing selected, the list of saved sections *is* what the
 * panel is for. It keeps the picker's row classes (`cb-template-picker__*`)
 * because the rows are identical — only the container moved. */
.cb-sidebar-library {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--cb-line);
  /* Long libraries scroll inside the panel rather than pushing the
     add-section buttons — the primary action — out of reach. */
  min-height: 0;
}
.cb-sidebar-library__label {
  margin: 0;
  color: var(--cb-text-2);
  font-family: var(--cb-font-mono);
  font-size: var(--cb-caption-size);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--cb-caption-tracking);
}
.cb-sidebar-library .cb-template-picker__list {
  /* Roomier than the flat list it replaced: the rows are thumbnail cards now,
     and at 40vh barely two of them fitted. */
  max-height: 55vh;
  overflow-y: auto;
}

.cb-replace-picker__search,
.cb-template-picker__search {
  width: 100%;
  padding: 0.375rem 0.5rem;
  background: var(--cb-field-bg);
  color: var(--cb-text);
  border: 1px solid var(--cb-line-strong);
  border-radius: var(--cb-radius-sm);
  font: inherit;
}
/* These two sit outside the form theme, so without this they fall back to the
   browser's default focus ring — a black box in the middle of the chrome. */
.cb-replace-picker__search:focus,
.cb-template-picker__search:focus {
  outline: none;
  border-color: var(--cb-accent);
  box-shadow: 0 0 0 3px rgba(var(--cb-accent-rgb), 0.18);
}

.cb-replace-picker__list,
.cb-template-picker__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
.cb-replace-picker__item,
.cb-template-picker__item {
  display: flex;
}
.cb-replace-picker__item-btn,
.cb-template-picker__item-btn {
  flex: 1 1 auto;
  text-align: left;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--cb-radius-xs);
  padding: 0.375rem 0.5rem;
  font: inherit;
  font-size: 0.9rem;
  color: var(--cb-text);
  cursor: pointer;
}
.cb-replace-picker__item-btn:hover,
.cb-replace-picker__item-btn:focus-visible,
.cb-template-picker__item-btn:hover,
.cb-template-picker__item-btn:focus-visible {
  background: rgba(var(--cb-accent-rgb), 0.08);
  border-color: rgba(var(--cb-accent-rgb), 0.25);
  outline: none;
}

.cb-replace-picker__status,
.cb-template-picker__status {
  margin: 0;
  color: var(--cb-muted);
  font-size: 0.85rem;
  min-height: 1rem;
}
.cb-replace-picker__status:empty,
.cb-template-picker__status:empty {
  display: none;
}

/* Section-template library picker: shares the replace picker's shell +
   item classes, adds a per-row delete affordance, a disabled state for
   incompatible templates, and a "load more" pagination control.
   Unlike the replace picker's rows, these are cards: a thumbnail drawn from
   the stored payload sits above the name (see .cb-template-poster below). */
/* Declared after the shared list rule above, which sets the replace picker's
   denser 0.125rem — cards need air between them, dense rows do not. */
.cb-template-picker__list {
  gap: 0.5rem;
}
.cb-template-picker__item {
  align-items: center;
  gap: 0.25rem;
  /* Anchors the delete button over the card's top-right corner — beside the
     name it would eat width the thumbnail needs. */
  position: relative;
}
.cb-template-picker__item-btn {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}
.cb-template-picker__item-name {
  /* One line: names are editor-typed and can run long, and a card that grows
     with its name breaks the grid rhythm the thumbnails create. */
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* These three matched `.cb-replace-picker__item-btn` until now — a class the
   library's rows never carry, so an incompatible template looked exactly like
   an insertable one. */
.cb-template-picker__item--disabled .cb-template-picker__item-btn {
  color: var(--cb-faint);
  cursor: not-allowed;
}
.cb-template-picker__item--disabled .cb-template-picker__item-btn:hover {
  background: transparent;
  border-color: transparent;
}
/* Partially usable: some of its blocks will be skipped. Still clickable — the
   marker warns without taking the useful insert away (the button's title spells
   out which types). */
.cb-template-picker__item--partial .cb-template-picker__item-name::after {
  content: "!";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  margin-left: 0.4rem;
  border-radius: 50%;
  background: var(--cb-warning);
  color: var(--cb-on-solid);
  font-size: 0.7rem;
  font-weight: 700;
  vertical-align: middle;
}
.cb-template-picker__delete {
  /* Floats over the thumbnail's corner. Kept permanently visible rather than
     revealed on hover: it is already in the tab order, and a control you can
     focus but not see is worse than one that is simply there. */
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 1;
  background: var(--cb-panel);
  border: 1px solid var(--cb-line);
  border-radius: var(--cb-radius-xs);
  padding: 0.25rem 0.375rem;
  font-size: 0.9rem;
  line-height: 1;
  color: var(--cb-faint);
  cursor: pointer;
}
.cb-template-picker__delete:hover,
.cb-template-picker__delete:focus-visible {
  background: rgba(var(--cb-danger-rgb), 0.1);
  border-color: rgba(var(--cb-danger-rgb), 0.3);
  color: var(--cb-danger);
  outline: none;
}
.cb-template-picker__more {
  margin-top: 0.25rem;
  align-self: center;
  background: transparent;
  border: 1px solid rgba(var(--cb-accent-rgb), 0.3);
  border-radius: var(--cb-radius-xs);
  padding: 0.375rem 0.75rem;
  font: inherit;
  font-size: 0.85rem;
  color: var(--cb-accent);
  cursor: pointer;
}
.cb-template-picker__more:hover,
.cb-template-picker__more:focus-visible {
  background: rgba(var(--cb-accent-rgb), 0.08);
  outline: none;
}

/* ---- Section-template thumbnail ("poster") ----
 *
 * Not a screenshot: a small diagram of the saved section, built from the
 * payload by SectionPosterBuilder and drawn as DOM by the builder controller.
 * Pictures are real <img> elements and headings real text, which is what lets
 * it work on any host (no rasterizer, no CSS inlining, nothing to store) and
 * on templates saved long before the feature existed.
 *
 * It reads as a *sketch*, deliberately: every tile is a flat neutral shape at
 * a size where no one could mistake it for the rendered section. */
.cb-template-poster {
  display: flex;
  gap: 3px;
  /* Height follows the content, within bounds. A fixed frame left a short
     section floating in dead space, which reads as a thumbnail that failed to
     load rather than as a short section. */
  min-height: 3.5rem;
  max-height: 12rem;
  padding: 4px;
  background: var(--cb-surface);
  border: 1px solid var(--cb-line);
  border-radius: var(--cb-radius-xs);
  overflow: hidden;
}
.cb-template-poster__col {
  /* flex-grow carries the column's real preset (col-4 → 4); basis 0 makes the
     ratio depend on that alone and not on the tiles inside. */
  flex: 0 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  overflow: hidden;
}
.cb-template-poster__tile {
  display: block;
  flex: 0 0 auto;
  min-height: 6px;
  padding: 2px 3px;
  border-radius: 2px;
  background: var(--cb-panel);
  color: var(--cb-text-2);
  font-size: 7px;
  line-height: 1.35;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cb-template-poster__tile--heading {
  font-weight: 700;
  font-size: 8px;
  color: var(--cb-text);
}
.cb-template-poster__tile--text {
  color: var(--cb-muted);
  /* Two lines of real copy, then cut — enough to tell two text blocks apart. */
  white-space: normal;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}
.cb-template-poster__tile--image {
  /* The one tile that claims leftover height: a picture is what the eye uses
     to recognise a section, so it gets the room the text tiles don't need —
     and, in a column of nothing but text tiles, sets the poster's height. */
  flex: 1 1 auto;
  min-height: 3.25rem;
  padding: 0;
  background: var(--cb-field-bg);
}
.cb-template-poster__tile--image img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 2px;
}
.cb-template-poster__tile--button {
  align-self: flex-start;
  max-width: 100%;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--cb-accent);
  color: var(--cb-on-solid);
  font-size: 7px;
}
.cb-template-poster__tile--rule {
  min-height: 0;
  height: 1px;
  padding: 0;
  margin: 2px 0;
  background: var(--cb-line-strong);
}
/* A block with nothing to preview says its own name rather than showing an
   empty shape — "Tableau" is information, a blank rectangle is not. */
.cb-template-poster__tile--generic {
  background: transparent;
  border: 1px dashed var(--cb-line-strong);
  color: var(--cb-faint);
}
/* Genuinely empty content (a title block with no title yet) — a plain bar,
   because there is nothing to name. */
.cb-template-poster__tile--blank {
  min-height: 5px;
  background: var(--cb-line);
}
/* A block type this build no longer registers. The card's tooltip lists them;
   the poster shows *where* they sit. */
.cb-template-poster__tile--missing {
  border: 1px dashed rgba(var(--cb-danger-rgb), 0.5);
  background: rgba(var(--cb-danger-rgb), 0.06);
  color: var(--cb-danger);
}
.cb-template-poster__more {
  flex: 0 0 auto;
  align-self: flex-start;
  color: var(--cb-faint);
  font-size: 7px;
  line-height: 1.4;
}

/* ---- Poster on a coloured section ----
 *
 * The section's own background (or its style preset's) is painted on the
 * frame, so a saved dark hero reads as a dark hero in the library rather than
 * as the same grey sketch as everything else.
 *
 * The tiles then have to give way: their default panel fill is opaque and
 * would tile white rectangles over the colour. Translucent white/black lets
 * the section's colour carry through while keeping each block legible as a
 * shape — and `--dark` flips the copy so it survives its own ground (the
 * server decided which, from the colour's luminance). */
.cb-template-poster--tinted {
  border-color: rgba(var(--cb-shadow-rgb), 0.18);
}
.cb-template-poster--tinted .cb-template-poster__tile {
  background: rgba(255, 255, 255, 0.55);
}
.cb-template-poster--tinted .cb-template-poster__tile--generic,
.cb-template-poster--tinted .cb-template-poster__tile--rule {
  background: transparent;
}
.cb-template-poster--tinted .cb-template-poster__tile--rule {
  background: rgba(var(--cb-shadow-rgb), 0.35);
}

.cb-template-poster--dark .cb-template-poster__tile {
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
}
.cb-template-poster--dark .cb-template-poster__tile--heading {
  color: #fff;
}
.cb-template-poster--dark .cb-template-poster__tile--text,
.cb-template-poster--dark .cb-template-poster__tile--generic,
.cb-template-poster--dark .cb-template-poster__more {
  color: rgba(255, 255, 255, 0.75);
}
.cb-template-poster--dark .cb-template-poster__tile--generic {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.35);
}
.cb-template-poster--dark .cb-template-poster__tile--rule {
  background: rgba(255, 255, 255, 0.4);
}
.cb-template-poster--dark .cb-template-poster__tile--blank {
  background: rgba(255, 255, 255, 0.28);
}
/* A tile with its own stored colour keeps it — the inline background beats
   both rule sets above, which is the intent: the block said so explicitly.
   Its copy then has to answer for itself, since a red card can be dark ground
   inside a light section. Declared last so it wins over the --dark block. */
.cb-template-poster .cb-template-poster__tile--on-dark,
.cb-template-poster--dark .cb-template-poster__tile--on-dark {
  color: #fff;
}
.cb-template-poster .cb-template-poster__tile--on-light,
.cb-template-poster--dark .cb-template-poster__tile--on-light {
  color: var(--cb-text);
}

/* ---- Import / Export topbar button + picker overlay ----
   Visually consistent with the Replace button & picker so the topbar
   reads as one cohesive toolbar. The picker sits in the same overlay
   stack (z-index 30) and is anchored to the topbar's left edge. */

.cb-import-export-picker {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 420px;
  max-width: calc(100% - 32px);
  max-height: calc(100% - 96px);
  display: flex;
  flex-direction: column;
  background: var(--cb-panel);
  border: 1px solid var(--cb-line);
  border-radius: var(--cb-radius);
  box-shadow: 0 18px 48px rgba(var(--cb-shadow-rgb), 0.28);
  z-index: 30;
  overflow: hidden;
}
.cb-import-export-picker[hidden] {
  display: none;
}
.cb-import-export-picker__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--cb-panel-2);
  background: var(--cb-panel-3);
}
.cb-import-export-picker__title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--cb-text);
}
.cb-import-export-picker__close {
  background: transparent;
  border: 0;
  font-size: 1.25rem;
  line-height: 1;
  padding: 0.15rem 0.35rem;
  cursor: pointer;
  color: var(--cb-muted);
}
.cb-import-export-picker__close:hover {
  color: var(--cb-text);
}
.cb-import-export-picker__body {
  padding: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  overflow-y: auto;
}
.cb-import-export-picker__section {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.cb-import-export-picker__heading {
  margin: 0;
  font-family: var(--cb-font-mono);
  font-size: var(--cb-caption-size);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--cb-caption-tracking);
  color: var(--cb-text-2);
}
.cb-import-export-picker__hint {
  margin: 0;
  color: var(--cb-muted);
  font-size: 0.85rem;
  line-height: 1.4;
}
.cb-import-export-picker__hint--warning {
  color: var(--cb-badge-fg);
}
.cb-import-export-picker__separator {
  border-top: 1px solid var(--cb-panel-2);
}
.cb-import-export-picker__file {
  font-size: 0.85rem;
}
.cb-import-export-picker__btn {
  align-self: flex-start;
  background: transparent;
  border: 1px solid var(--cb-line-strong);
  border-radius: var(--cb-radius-sm);
  padding: 0.4rem 0.85rem;
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
  color: var(--cb-text);
}
.cb-import-export-picker__btn:hover:not([disabled]) {
  background: rgba(var(--cb-accent-rgb), 0.08);
  border-color: rgba(var(--cb-accent-rgb), 0.4);
}
.cb-import-export-picker__btn--primary {
  background: var(--cb-accent);
  border-color: var(--cb-accent);
  color: var(--cb-on-solid);
}
.cb-import-export-picker__btn--primary:hover:not([disabled]) {
  background: var(--cb-accent-strong);
  border-color: var(--cb-accent-strong);
  color: var(--cb-on-solid);
}
.cb-import-export-picker__status {
  margin: 0;
  color: var(--cb-muted);
  font-size: 0.85rem;
  min-height: 1rem;
}
.cb-import-export-picker__status:empty {
  display: none;
}

.cb-shell__viewport {
  display: inline-flex;
  background: var(--cb-surface);
  border-radius: var(--cb-radius-sm);
  padding: 0.125rem;
}
.cb-shell__viewport-btn {
  background: transparent;
  border: 0;
  padding: 0.25rem 0.75rem;
  cursor: pointer;
  color: var(--cb-muted);
  border-radius: var(--cb-radius-xs);
  font-size: 1.1rem;
  line-height: 1;
}
.cb-shell__viewport-btn[hidden] {
  display: none;
}
.cb-shell__viewport-btn--active {
  background: var(--cb-panel);
  color: var(--cb-text);
  box-shadow: 0 1px 2px rgba(var(--cb-shadow-rgb), 0.08);
}
/* Hairline in the gutter between the viewport switcher and the
   discard/publish pair — they share the right cluster but not the same job:
   one changes what you look at, the others commit what you changed. Drawn as
   a pseudo-element so the separator needs no node of its own; `right` is half
   the 1rem gutter (0.5rem flex gap + 0.5rem margin) so it lands centred. */
.cb-shell__topbar-right .cb-shell__viewport {
  position: relative;
  margin-right: 0.5rem;
}
.cb-shell__topbar-right .cb-shell__viewport::after {
  content: "";
  position: absolute;
  top: 50%;
  right: -0.5rem;
  transform: translateY(-50%);
  width: 1px;
  height: 1.25rem;
  background: var(--cb-line);
}

/* ---- Save-failure banner (topbar) ----
 *
 * Persistent (unlike the transient sidebar "Saved" flash): it appears
 * when any save path fails — structural AJAX op, Live block form,
 * section settings POST — and only disappears once a later save
 * succeeds. Red chip sitting left of the Discard / Publish buttons.
 */
.cb-shell__save-error {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid rgba(var(--cb-danger-rgb), 0.4);
  border-radius: var(--cb-radius-sm);
  background: rgba(var(--cb-danger-rgb), 0.08);
  color: var(--cb-danger-strong);
  font-size: 0.8125rem;
  line-height: 1.2;
  white-space: nowrap;
}
.cb-shell__save-error[hidden] {
  display: none;
}
@media (max-width: 768px) {
  /* Keep the chip readable on narrow topbars: icon only. */
  .cb-shell__save-error > span:not(.cb-shell__btn-icon) {
    display: none;
  }
}

/* ---- Undo snackbar ----
 *
 * Floating chip at the bottom-center of the shell, shown for a few
 * seconds after a block/section delete. Dark (inverse) styling so it
 * reads as transient system feedback, not part of the page.
 */
.cb-shell__undo {
  position: absolute;
  bottom: 1.25rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 40;
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem 0.5rem 1rem;
  border-radius: var(--cb-radius);
  background: var(--cb-text);
  color: var(--cb-on-solid);
  font-size: 0.875rem;
  line-height: 1.2;
  box-shadow: 0 4px 16px rgba(var(--cb-shadow-rgb), 0.25);
  white-space: nowrap;
}
.cb-shell__undo[hidden] {
  display: none;
}
.cb-shell__undo-btn {
  background: transparent;
  border: 0;
  padding: 0.25rem 0.5rem;
  border-radius: var(--cb-radius-sm);
  color: var(--cb-accent-soft);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.cb-shell__undo-btn:hover {
  background: rgba(var(--cb-accent-soft-rgb), 0.12);
}

/* ---- Top-right action buttons (Discard / Publish) ---- */

.cb-shell__discard,
.cb-shell__publish {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.375rem 0.75rem;
  border-radius: var(--cb-radius-sm);
  font: inherit;
  font-size: 0.9rem;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background-color 0.12s ease,
    border-color 0.12s ease,
    color 0.12s ease;
}
.cb-shell__discard {
  background: transparent;
  border: 1px solid transparent;
  color: var(--cb-accent);
}
.cb-shell__discard:hover:not([disabled]) {
  background: rgba(var(--cb-accent-rgb), 0.08);
  border-color: rgba(var(--cb-accent-rgb), 0.25);
}
.cb-shell__discard[hidden] {
  display: none;
}
/* The topbar's primary action, so it takes the chrome accent rather than the
   success green: green is reserved for reporting an outcome (the "saved" pill
   below, upload status), not for offering an action. */
.cb-shell__publish {
  background: var(--cb-accent);
  border: 1px solid var(--cb-accent);
  color: var(--cb-on-solid);
}
.cb-shell__publish:hover:not([disabled]) {
  background: var(--cb-accent-strong);
  border-color: var(--cb-accent-strong);
}
.cb-shell__publish[disabled] {
  background: var(--cb-line-strong);
  border-color: var(--cb-line-strong);
  color: var(--cb-on-solid);
  cursor: not-allowed;
}
.cb-shell__btn-icon {
  font-size: 1rem;
  line-height: 1;
}
/* Default state: icon hidden on desktop where the label is shown. */
.cb-shell__discard .cb-shell__btn-icon {
  display: none;
}

/* ---- Mobile tightening ---- */
@media (max-width: 768px) {
  /* Drop the grid on mobile and switch to flex with space-between. The grid
       was forcing the right column to a fixed 1fr share, so when discard+
       publish were wider than that share the buttons spilled past the
       topbar. Flex lets each cluster take its natural width. */
  .cb-shell__topbar {
    display: flex;
    justify-content: space-between;
    padding: 0 0.5rem;
    gap: 0.5rem;
  }
  .cb-shell__topbar-left,
  .cb-shell__topbar-right {
    justify-self: auto;
    flex: 0 0 auto;
  }
  /* Right cluster gets crowded here — drop the separator and its gutter and
     let the plain 0.5rem flex gap do the spacing. */
  .cb-shell__topbar-right .cb-shell__viewport {
    margin-right: 0;
  }
  .cb-shell__topbar-right .cb-shell__viewport::after {
    display: none;
  }
  /* Discard collapses to icon-only — the long "Annuler les modifications"
       label was wrapping on narrow screens and pushing Publish out of the
       topbar row. */
  .cb-shell__discard {
    padding: 0.375rem 0.5rem;
  }
  .cb-shell__discard .cb-shell__btn-icon {
    display: inline-flex;
    font-size: 1.15rem;
  }
  .cb-shell__discard .cb-shell__btn-label {
    display: none;
  }
  /* The Actions toggle keeps its word — it is the only thing naming what is
     behind it, and the menu rows below it are already labelled. */
  .cb-shell__actions-toggle {
    padding: 0.375rem 0.5rem;
  }
  .cb-shell__actions-list {
    min-width: 200px;
    max-width: calc(100vw - 24px);
  }
  .cb-replace-picker,
  .cb-import-export-picker {
    width: calc(100% - 16px);
    max-height: calc(100% - 32px);
  }
  .cb-shell__publish {
    padding: 0.375rem 0.65rem;
    font-size: 0.85rem;
  }
  .cb-shell__viewport-btn {
    padding: 0.25rem 0.55rem;
  }
}

/* ---- Main area: 2-column grid (sidebar left, iframe right) ----
 *
 * The sidebar is permanent: it always claims a column rather than
 * floating over the iframe. A collapsed state shrinks the column to
 * a fly-out handle width so the iframe can expand to the full main
 * width when the user wants a wide preview. Width is CSS-variable
 * driven so the resize handle can update one knob without touching
 * the grid template directly. */

.cb-shell__main {
  display: grid;
  grid-template-columns: var(--cb-sidebar-width) 1fr;
  overflow: hidden;
  /* Dot grid on the canvas. Only ever visible in the gutters either side of
     the iframe — i.e. when the viewport switcher narrows it to tablet or
     mobile — where it reads as "this is the stage, not the page". */
  background:
    radial-gradient(circle at 1px 1px, var(--cb-line-strong) 1px, transparent 0) 0 0 / 24px 24px,
    var(--cb-surface);
  min-height: 0;
}

.cb-shell--sidebar-collapsed .cb-shell__main {
  grid-template-columns: var(--cb-sidebar-collapsed-width) 1fr;
}

.cb-shell__iframe {
  border: 0;
  background: var(--cb-panel);
  width: 100%;
  height: 100%;
  display: block;
  /* Smooth resize when the viewport switcher changes max-width — the
       transition gives the iframe content time to reflow with media
       queries, instead of flashing a horizontal scrollbar at the moment
       the size jumps. */
  transition:
    max-width 0.2s ease,
    margin 0.2s ease;
}

.cb-shell__sidebar {
  position: relative;
  background: var(--cb-panel);
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--cb-line);
  /* Accent rule along the sidebar's top edge — the one place the chrome
     accent appears as a surface rather than as text or a control. */
  box-shadow:
    inset 0 3px 0 var(--cb-accent),
    2px 0 12px rgba(var(--cb-shadow-rgb), 0.04);
  min-width: 0;
  /* min-height:0 stops this grid item from growing past its track when the
       form is tall — without it the implicit row expands to fit the content
       and .cb-shell__main's overflow:hidden clips the bottom, so the
       sidebar-content's overflow-y:auto never engages and lower fields
       become unreachable.
     No overflow:hidden here — the toggle button is absolute-positioned and
       deliberately overhangs the right edge; the content child scrolls. */
  min-height: 0;
}

/* Collapsed: hide content, resize handle, saved-flash; only the
   toggle chip remains visible to expand back. */
.cb-shell--sidebar-collapsed .cb-shell__sidebar-content,
.cb-shell--sidebar-collapsed .cb-shell__sidebar-resize,
.cb-shell--sidebar-collapsed .cb-shell__sidebar-saved {
  display: none;
}

.cb-shell__sidebar-resize {
  position: absolute;
  top: 0;
  bottom: 0;
  right: -3px;
  width: 6px;
  cursor: col-resize;
  z-index: 6;
  transition: background-color 0.12s ease-out;
}
.cb-shell__sidebar-resize:hover,
.cb-shell__sidebar-resize:active {
  background-color: rgba(var(--cb-accent-rgb), 0.35);
}

/* Floating toggle chip on the sidebar's right edge, vertically centered.
   Translates half its width past the sidebar so it overhangs the iframe
   gutter — still entirely clickable, and visible even when the sidebar
   is collapsed to its 32px stub. */
.cb-shell__sidebar-toggle {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translate(50%, -50%);
  z-index: 10;
  width: 26px;
  height: 26px;
  background: var(--cb-panel);
  border: 1px solid var(--cb-line-strong);
  border-radius: 50%;
  box-shadow: 0 1px 4px rgba(var(--cb-shadow-rgb), 0.12);
  color: var(--cb-muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.cb-shell__sidebar-toggle:hover {
  background: var(--cb-surface);
  color: var(--cb-text);
}
.cb-shell__sidebar-toggle-icon {
  display: inline-block;
  transition: transform 0.15s ease-out;
}
.cb-shell--sidebar-collapsed .cb-shell__sidebar-toggle-icon {
  transform: rotate(180deg);
}

/* Mobile: the sidebar becomes a bottom sheet that floats above the
   iframe instead of stealing a row from the main grid. The iframe gets
   the full main area and the sidebar slides in from the bottom edge,
   overlaying the lower portion of the preview. When collapsed it
   translates down to leave just a 32px strip visible, holding the
   toggle that pulls it back up. cb-builder also scrolls the iframe
   so the focused element's bottom isn't hidden under the sheet.

   This block lives AFTER the desktop sidebar/toggle rules on purpose:
   media queries don't bump specificity, so the cascade still picks the
   last matching declaration — putting mobile last lets it win at
   <=768px without resorting to !important. */
@media (max-width: 768px) {
  .cb-shell__main {
    position: relative;
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
  }
  .cb-shell--sidebar-collapsed .cb-shell__main {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
  }
  .cb-shell__sidebar {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 60vh;
    border-right: 0;
    border-top: 1px solid var(--cb-line);
    box-shadow: 0 -2px 12px rgba(var(--cb-shadow-rgb), 0.08);
    z-index: 5;
    transition: transform 0.22s ease-out;
  }
  .cb-shell--sidebar-collapsed .cb-shell__sidebar {
    /* Slide the sheet down so only the 32px strip carrying the toggle
       remains visible above the bottom edge. The translate keeps the
       layout height intact so getBoundingClientRect reports the visual
       (transformed) position — handy when we measure for auto-scroll. */
    transform: translateY(calc(100% - 32px));
  }
  .cb-shell__sidebar-resize {
    display: none;
  }
  /* Toggle pinned to the top edge of the sheet, centered horizontally,
     overhanging upward so it reads as a "grab handle" on a bottom
     sheet — same chip in both states, just moves with the sheet. */
  .cb-shell__sidebar-toggle {
    top: 0;
    right: auto;
    left: 50%;
    transform: translate(-50%, -50%);
  }
  /* The chevron is a left-arrow at rest; rotate it so it reads as
     "down" when expanded (close direction) and "up" when collapsed
     (pull-up affordance). */
  .cb-shell__sidebar-toggle-icon {
    transform: rotate(-90deg);
  }
  .cb-shell--sidebar-collapsed .cb-shell__sidebar-toggle-icon {
    transform: rotate(90deg);
  }
}

/* Transient "✓ Saved" pill pinned to the sidebar's top-right corner,
   fired by autosave. role="status" + aria-live="polite" handles the
   screen-reader announcement; the visible pill fades in/out via a
   transition. */
.cb-shell__sidebar-saved {
  position: absolute;
  top: 0.5rem;
  right: 0.75rem;
  z-index: 5;
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.55rem;
  border-radius: 999px;
  background: rgba(var(--cb-success-rgb), 0.12);
  color: var(--cb-success);
  font-size: 0.8rem;
  font-weight: 500;
  line-height: 1;
  opacity: 0;
  transition: opacity 0.25s ease-out;
}
.cb-shell__sidebar-saved.is-visible {
  opacity: 1;
}
.cb-shell__sidebar-saved[hidden] {
  display: none;
}

.cb-shell__sidebar-content {
  flex: 1 1 auto;
  /* min-height: 0 lets the flex child shrink past its content size
       so overflow-y: auto can scroll inside the sidebar. Without it the
       child would expand to fit content and push past the sidebar. */
  min-height: 0;
  overflow-y: auto;
  padding: 1rem;
}

/* Empty state shown when no block/section is focused. Keeps the
   sidebar useful even before the user clicks anything: a one-liner
   hint plus the three add-section buttons (mirror of the iframe's
   own empty tray). */
.cb-sidebar-empty {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}
.cb-sidebar-empty__hint {
  margin: 0;
  color: var(--cb-muted);
  font-size: 0.9rem;
  line-height: 1.4;
}
.cb-sidebar-empty__sections {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.cb-sidebar-empty__sections-label {
  margin: 0;
  color: var(--cb-text);
  font-size: 0.85rem;
  font-weight: 600;
}
.cb-sidebar-empty__buttons {
  display: flex;
  gap: 0.5rem;
}
.cb-sidebar-empty__btn {
  background: var(--cb-panel);
  border: 1px solid var(--cb-line-strong);
  border-radius: var(--cb-radius-sm);
  color: var(--cb-muted);
  cursor: pointer;
  padding: 0.5rem;
  line-height: 0;
  transition:
    border-color 0.12s ease,
    color 0.12s ease,
    background-color 0.12s ease;
}
.cb-sidebar-empty__btn:hover,
.cb-sidebar-empty__btn:focus-visible {
  border-color: var(--cb-accent);
  color: var(--cb-accent);
  background: rgba(var(--cb-accent-rgb), 0.06);
  outline: none;
}

/* (The visually-hidden helper lives in forms.css as .cb-form-sr-only —
   used by the in-form save buttons that the header Save delegates to.) */

/* (Add-section UI now lives inside the iframe — see builder.css's
   .cb-add-section-tray.) */

/* ---- Sidebar groups (General + Styling stacked) ----
 *
 * Replaces the old tab pattern: both blocks are always visible and the
 * sidebar content scrolls when they overflow. Each group is introduced
 * by a small uppercase label and separated by a thin divider so the
 * eye can still scan the two clusters at a glance. */

.cb-sidebar__group {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--cb-panel-2);
}
.cb-sidebar__group:first-of-type {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.cb-sidebar__group-title {
  margin: 0 0 0.75rem;
  /* Same family, case and size as the field labels below it — a group title
     outranks them by weight and color, not by a third size in the scale.
     Going lighter (as a "caption" would) inverted the hierarchy: the heading
     read as the least important text in its own group. */
  color: var(--cb-text-2);
  font-family: var(--cb-font-mono);
  font-size: var(--cb-caption-size);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--cb-caption-tracking);
}

.cb-sidebar__group-body > :last-child {
  margin-bottom: 0;
}

/* ---- Block edit form: heading ----
 *
 * Lightweight title above the fields, showing which block is being edited.
 * Icon (block-author SVG) sits in a soft accent-tinted chip. */

.cb-block__heading {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 0.5rem;
}

.cb-block__heading-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  color: var(--cb-accent);
  background: rgba(var(--cb-accent-rgb), 0.1);
}

.cb-block__heading-icon svg {
  width: 16px;
  height: 16px;
}

.cb-block__heading-label {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--cb-text);
  letter-spacing: 0.01em;
}

/* ---- Block edit form: tabbed field groups ----
 *
 * Block fields are bucketed into tabs by their `data-cb-group` attribute
 * (rendered in components/Block.html.twig). A horizontal tablist sits above
 * the panels; only the active panel is shown. All panels stay in the DOM so
 * autosave and validation keep working across hidden tabs. */

.cb-block__tabs {
  display: flex;
  flex-direction: column;
}

/* The tablist stays visible while the (now scrollable) sidebar scrolls a
   tall form, so the user can switch tabs from anywhere. It sticks to the
   top of the scroll container (.cb-shell__sidebar-content); the negative
   top + matching padding swallow the container's 1rem padding so no field
   peeks above it, and the white background hides rows scrolling underneath.
   Reachability itself is handled by .cb-shell__sidebar { min-height: 0 },
   which lets sidebar-content's overflow-y:auto engage on tall content. */
.cb-block__tablist {
  position: sticky;
  top: -1rem;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  margin-bottom: 1rem;
  padding-top: 1rem;
  background: var(--cb-panel);
  border-bottom: 1px solid var(--cb-line);
}

/* Folder tabs rather than an underlined word.
 *
 * These switch the whole panel, so they have to read as a control at a glance
 * — an underline on body text does not, least of all above a column of mono
 * captions that outshouts it. Giving them a shape also keeps the segmented
 * pill (.cb-viewport-tabs, .cb-align-group) meaning one thing only: pick a
 * value inside a field, never navigate away from one.
 *
 * The active tab is the only one filled with the panel color and it paints
 * over the strip's bottom line, so it reads as continuous with the fields
 * below it. Its top edge carries the accent — the same gesture that marks the
 * top of the sidebar. */
.cb-block__tab {
  appearance: none;
  padding: 6px 12px;
  /* Sit on the strip's bottom line so the active tab can cover it. */
  margin-bottom: -1px;
  border: 1px solid var(--cb-line);
  border-top-width: 2px;
  border-radius: var(--cb-radius-sm) var(--cb-radius-sm) 0 0;
  background: var(--cb-field-bg);
  color: var(--cb-muted);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.cb-block__tab:hover:not(.cb-block__tab--active) {
  background: var(--cb-panel-2);
  color: var(--cb-text);
}

.cb-block__tab--active {
  background: var(--cb-panel);
  border-top-color: var(--cb-accent);
  border-bottom-color: var(--cb-panel);
  color: var(--cb-text);
  font-weight: 700;
}

/* Red dot appended to a tab whose panel holds an invalid field, so a
   validation error on a hidden tab is still discoverable after a save. */
.cb-block__tab--has-error::after {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: 5px;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--cb-danger);
}

.cb-block__tabpanel[hidden] {
  display: none;
}

/* ---- Styling: responsive box spacing (padding / margin) ---- */

.cb-form-row__header {
  margin-bottom: 0.375rem;
}

.cb-viewport-tabs {
  display: inline-flex;
  gap: 2px;
  background: var(--cb-panel-2);
  border-radius: var(--cb-radius-sm);
  padding: 2px;
}

.cb-viewport-tabs__btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--cb-muted);
  padding: 2px 8px;
  border-radius: var(--cb-radius-xs);
  font-size: 12px;
  line-height: 1.4;
  cursor: pointer;
}

.cb-viewport-tabs__btn--active {
  background: var(--cb-panel);
  color: var(--cb-text);
  box-shadow: 0 1px 2px rgba(var(--cb-shadow-rgb), 0.12);
}

.cb-responsive-box-spacing__viewport[hidden] {
  display: none;
}

/* ---- Styling: 4-side box spacing ---- */

.cb-box-spacing {
  border: 1px solid var(--cb-line);
  border-radius: var(--cb-radius-sm);
  padding: 0.5rem;
  background: var(--cb-panel-3);
}

.cb-box-spacing__sides {
  display: grid;
  grid-template-columns: repeat(4, 1fr) auto;
  gap: 0.375rem;
  align-items: end;
}

.cb-box-spacing__side {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.cb-box-spacing__label {
  font-family: var(--cb-font-mono);
  font-size: var(--cb-caption-size-sm);
  color: var(--cb-faint);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  text-align: center;
  order: 2;
  margin: 0;
}

.cb-box-spacing__input {
  text-align: center;
  order: 1;
}

.cb-box-spacing__link {
  appearance: none;
  border: 1px solid var(--cb-line-strong);
  background: var(--cb-panel);
  color: var(--cb-muted);
  width: 28px;
  height: 28px;
  border-radius: var(--cb-radius-xs);
  align-self: start;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 0;
}

.cb-box-spacing--linked .cb-box-spacing__link,
.cb-box-spacing__link[aria-pressed="true"] {
  background: var(--cb-text);
  color: var(--cb-on-solid);
  border-color: var(--cb-text);
}

/* ---- Styling: length input (value + unit) ---- */

.cb-length {
  max-width: 160px;
}

.cb-length__unit {
  max-width: 72px;
  flex: 0 0 auto;
}

/* ---- Styling: alignment icon-radio group ---- */

.cb-align-group {
  display: inline-flex;
  gap: 2px;
  background: var(--cb-panel-2);
  border-radius: var(--cb-radius-sm);
  padding: 2px;
}

.cb-align-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 28px;
  border-radius: var(--cb-radius-xs);
  color: var(--cb-muted);
  cursor: pointer;
  margin: 0;
  transition:
    background-color 0.08s,
    color 0.08s;
}

.cb-align-btn:hover {
  background: rgba(var(--cb-on-solid-rgb), 0.6);
  color: var(--cb-text);
}

.cb-align-btn:has(.cb-align-btn__input:checked),
.cb-align-btn--active {
  background: var(--cb-panel);
  color: var(--cb-text);
  box-shadow: 0 1px 2px rgba(var(--cb-shadow-rgb), 0.12);
}

.cb-align-btn:has(.cb-align-btn__input:focus-visible) {
  outline: 2px solid var(--cb-accent-soft);
  outline-offset: 1px;
}

.cb-align-btn__face {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
