/* Forms */
.form-group {
  margin-bottom: 1.25rem;
}

.form-label {
  display: block;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-700);
  margin-bottom: 0.25rem;
}

.form-input {
  width: 100%;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--color-gray-300);
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: 0.875rem;
  transition: border-color 0.15s ease-in-out;
  box-sizing: border-box;
}

.form-input:focus {
  outline: none;
  border-color: var(--color-pink-base);
  box-shadow: 0 0 0 3px rgba(236, 72, 153, 0.1);
}

.form-error {
  color: #ef4444;
  font-size: 0.75rem;
  margin-top: 0.25rem;
  display: block;
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  padding: 0.625rem 1.25rem;
  font-weight: 500;
  border-radius: var(--radius-md);
  font-size: 0.875rem;
  cursor: pointer;
  transition: all 0.2s;
  border: none;
}

.btn-primary {
  background-color: var(--color-pink-base);
  color: white;
}

.btn-primary:hover {
  background-color: var(--color-pink-light);
}

/* Page Components */
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 2rem;
}

.page-title {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--color-gray-900);
  margin: 0;
}

/* Cards */
.metrics-grid {
  display: grid;
  grid-template-columns: repeat(1, 1fr);
  gap: 1.5rem;
}

@media (min-width: 640px) {
  .metrics-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .metrics-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

.metric-card {
  background: white;
  border-radius: var(--radius-lg);
  padding: 1.5rem;
  box-shadow: var(--shadow-sm);
  border: 1px solid var(--color-gray-200);
}

.metric-title {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-gray-500);
  margin: 0 0 0.5rem 0;
}

.metric-value {
  font-size: 1.875rem;
  font-weight: 700;
  color: var(--color-gray-900);
  font-family: var(--font-serif);
  margin-bottom: 0.5rem;
}

.metric-trend {
  font-size: 0.875rem;
  font-weight: 500;
}

.trend-up {
  color: var(--color-teal-base);
}

.trend-down {
  color: #ef4444;
}

/* Charts */
.charts-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
  margin-top: 2rem;
}

@media (min-width: 1024px) {
  .charts-grid {
    grid-template-columns: 2fr 1fr;
  }
}

.chart-card {
  background: white;
  border-radius: var(--radius-lg);
  padding: 1.5rem;
  box-shadow: var(--shadow-sm);
  border: 1px solid var(--color-gray-200);
}

.chart-title {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--color-gray-900);
  margin-bottom: 1.5rem;
  font-family: var(--font-serif);
}

.chart-container {
  position: relative;
  height: 300px;
  width: 100%;
}

/* =========================================================================
   Slide-over panels
   =========================================================================
   The panel's primary navigation gesture: a detail surface slides in from the
   right over the list it came from, and a back arrow slides it out again.

   Driven entirely by the `data-open` attribute on the root, so the only thing
   Alpine has to do is flip a boolean — no JS-computed styles, no per-frame
   work. The `visibility` transition is what lets a purely attribute-driven
   panel animate on the way OUT as well as in: visibility flips discretely, so
   delaying it by the exit duration keeps the panel painted while it slides
   away, then hides it so it cannot swallow clicks.

   Note the existing x-admin.modal component animates nothing — it was written
   with Tailwind utility class names (opacity-0, translate-y-4) that don't exist
   in this stylesheet, so both its transitions are inert. Hence real CSS here.
   ========================================================================= */
.slide-over {
  position: fixed;
  inset: 0;
  z-index: 9998;
  visibility: hidden;
  transition: visibility 0s linear var(--dur-panel-out);
}

.slide-over[data-open='true'] {
  visibility: visible;
  transition-delay: 0s;
}

.slide-over-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(31 32 18 / 0.42);
  backdrop-filter: blur(2px);
  opacity: 0;
  transition: opacity var(--dur-panel-out) linear;
}

.slide-over[data-open='true'] .slide-over-backdrop {
  opacity: 1;
  transition-duration: var(--dur-panel-in);
}

.slide-over-panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(var(--slide-over-width, 980px), 100%);
  display: flex;
  flex-direction: column;
  background: var(--cream);
  box-shadow: var(--shadow-lg);
  transform: translateX(100%);
  transition: transform var(--dur-panel-out) var(--ease-panel-out);
  /* Promoted for the duration of the gesture only; leaving a permanent
     compositing layer on a full-height panel costs memory for nothing. */
  will-change: transform;
}

.slide-over[data-open='true'] .slide-over-panel {
  transform: translateX(0);
  transition: transform var(--dur-panel-in) var(--ease-panel-in);
}

.slide-over-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 24px;
  background: var(--white);
  border-bottom: 1px solid var(--border-subtle);
}

.slide-over-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  padding: 8px 14px 8px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--white);
  color: var(--text-body);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}

.slide-over-back:hover {
  background: var(--sand-50);
  border-color: var(--sand-400);
}

.slide-over-back:active {
  transform: scale(0.97);
}

.slide-over-back svg {
  width: 16px;
  height: 16px;
  transition: transform 0.15s var(--ease-panel-in);
}

.slide-over-back:hover svg {
  transform: translateX(-2px);
}

.slide-over-title {
  min-width: 0;
  flex: 1;
}

.slide-over-title-main {
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 600;
  color: var(--text-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.slide-over-title-sub {
  font-size: 13px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.slide-over-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 22px 24px 32px;
}

/* The content settles in a beat behind the panel edge, which reads as the panel
   arriving with something in it rather than as a blank sheet that fills.

   On the way out it fades over the full exit duration rather than snapping:
   with an instant transition here the content vanished on the first frame of the
   close and what slid away was an empty white sheet — the exact thing the
   server-side content hold-over (see the slide-over component) exists to avoid. */
.slide-over-body > * {
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity var(--dur-panel-out) ease-in,
    transform var(--dur-panel-out) var(--ease-panel-out);
}

.slide-over[data-open='true'] .slide-over-body > * {
  opacity: 1;
  transform: none;
  transition: opacity 260ms ease-out 120ms, transform 260ms var(--ease-panel-in) 120ms;
}

@media (prefers-reduced-motion: reduce) {
  .slide-over,
  .slide-over-backdrop,
  .slide-over-panel,
  .slide-over-body > * {
    transition-duration: 0s !important;
    transition-delay: 0s !important;
  }

  .slide-over-panel {
    transform: translateX(0);
  }

  .slide-over[data-open='false'] .slide-over-panel {
    transform: translateX(100%);
  }
}

/* =========================================================================
   Lifecycle tracker
   ========================================================================= */
.lifecycle-stepper {
  display: flex;
  align-items: flex-start;
  gap: 0;
  padding: 14px 16px;
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}

.lifecycle-stepper-stage {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  position: relative;
}

.lifecycle-stepper-rail {
  display: flex;
  align-items: center;
  gap: 0;
}

.lifecycle-dot {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  border: 2px solid var(--sand-300);
  background: var(--white);
  color: var(--text-muted);
}

.lifecycle-line {
  flex: 1;
  height: 2px;
  background: var(--sand-200);
}

.lifecycle-stepper-stage[data-state='done'] .lifecycle-dot {
  background: var(--olive-600);
  border-color: var(--olive-600);
  color: var(--white);
}

.lifecycle-stepper-stage[data-state='done'] .lifecycle-line {
  background: var(--olive-600);
}

.lifecycle-stepper-stage[data-state='current'] .lifecycle-dot {
  border-color: var(--pink-500);
  color: var(--pink-600);
  box-shadow: 0 0 0 4px var(--pink-50);
}

.lifecycle-stepper-stage[data-state='blocked'] .lifecycle-dot {
  border-color: var(--danger);
  background: var(--danger-bg);
  color: var(--danger);
}

.lifecycle-stepper-stage[data-state='dormant'] .lifecycle-dot {
  border-style: dashed;
}

.lifecycle-stepper-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--text-strong);
  padding-right: 10px;
}

.lifecycle-stepper-stage[data-state='dormant'] .lifecycle-stepper-label,
.lifecycle-stepper-stage[data-state='pending'] .lifecycle-stepper-label {
  color: var(--text-muted);
  font-weight: 600;
}

.lifecycle-stepper-detail {
  font-size: 11.5px;
  color: var(--text-muted);
  padding-right: 10px;
  line-height: 1.45;
}

/* Checklist density — the actionable form used in the Verification Center. */
.lifecycle-stage-card {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--white);
  overflow: hidden;
  margin-bottom: 10px;
}

.lifecycle-stage-card[data-state='blocked'] {
  border-color: color-mix(in oklab, var(--danger) 35%, transparent);
}

.lifecycle-stage-card[data-state='current'] {
  border-color: color-mix(in oklab, var(--pink-500) 35%, transparent);
  box-shadow: var(--shadow-sm);
}

.lifecycle-stage-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border-subtle);
}

.lifecycle-stage-card[data-state='dormant'] .lifecycle-stage-head {
  background: var(--white);
}

.lifecycle-stage-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-strong);
}

.lifecycle-steps {
  list-style: none;
  margin: 0;
  padding: 0;
}

.lifecycle-step {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 14px;
  border-top: 1px solid var(--border-soft);
}

.lifecycle-step:first-child {
  border-top: none;
}

.lifecycle-step[data-state='blocked'] {
  background: var(--danger-bg);
}

.lifecycle-step-mark {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 1px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  border: 1.5px solid var(--sand-300);
  color: var(--text-muted);
  background: var(--white);
}

.lifecycle-step[data-state='done'] .lifecycle-step-mark {
  background: var(--success);
  border-color: var(--success);
  color: var(--white);
}

.lifecycle-step[data-state='current'] .lifecycle-step-mark {
  border-color: var(--pink-500);
  color: var(--pink-600);
}

.lifecycle-step[data-state='blocked'] .lifecycle-step-mark {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--white);
}

.lifecycle-step-body {
  min-width: 0;
  flex: 1;
}

.lifecycle-step-label {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-strong);
  line-height: 1.4;
}

.lifecycle-step[data-state='dormant'] .lifecycle-step-label,
.lifecycle-step[data-state='pending'] .lifecycle-step-label {
  font-weight: 500;
  color: var(--text-body);
}

.lifecycle-step-detail {
  font-size: 12.5px;
  color: var(--text-muted);
  line-height: 1.5;
  margin-top: 2px;
  word-break: break-word;
}

/* Chip density — one glance, inside a table cell or queue row. */
.lifecycle-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px 3px 4px;
  border-radius: var(--radius-pill);
  background: var(--sand-100);
  color: var(--sand-700);
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}

.lifecycle-chip-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: var(--white);
  font-size: 10px;
  font-weight: 700;
}

.lifecycle-chip[data-state='done'] {
  background: var(--success-bg);
  color: var(--success);
}

.lifecycle-chip[data-state='current'] {
  background: var(--pink-50);
  color: var(--pink-600);
}

.lifecycle-chip[data-state='blocked'] {
  background: var(--danger-bg);
  color: var(--danger);
}

/* =========================================================================
   Verification queue dock
   =========================================================================
   Nine queues rendered as a wrapped pill row took five lines and a third of the
   column height before any campaign was visible. This collapses them behind the
   one you are in: the trigger always states the current queue, its size, and how
   much work is waiting in the others, so nothing is hidden — only folded.
   ========================================================================= */
/* Pinned to the top of the scrolling queue column.
   The list can run to a hundred campaigns; a control that scrolls away means
   scrolling back up to switch queue, every time. Sticky rather than fixed so it
   stays inside the column and needs no knowledge of the sidebar's width. */
.queue-dock {
  position: sticky;
  top: 0;
  z-index: 5;
  /* Opaque: rows scroll underneath it. */
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border-subtle);
  /* A hairline plus a soft drop reads as a layer above the list rather than a
     bar welded to the top of the column. */
  box-shadow: 0 4px 10px -8px rgb(31 32 18 / 0.35);
}

.queue-dock-trigger {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 11px 14px;
  border: none;
  background: transparent;
  color: var(--text-strong);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease;
}

.queue-dock-trigger:hover {
  background: var(--sand-100);
}

.queue-dock-current {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}

.queue-dock-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.queue-dock-elsewhere {
  margin-left: auto;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
}

.queue-dock-chevron {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  color: var(--text-muted);
  transition: transform var(--dur-panel-out) var(--ease-panel-in);
}

.queue-dock.is-open .queue-dock-chevron {
  transform: rotate(180deg);
}

/* Grid-rows is what lets an auto-height list animate; max-height would need a
   guessed number that clips or lags depending on how many queues exist. */
.queue-dock-panel {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  background: var(--white);
  transition: grid-template-rows var(--dur-panel-out) var(--ease-panel-in);
}

.queue-dock.is-open .queue-dock-panel {
  grid-template-rows: 1fr;
  border-top: 1px solid var(--border-subtle);
}

/* The single grid child. It must clip its own overflow, because at 0fr the row
   has no height but the content inside it still wants to paint. */
.queue-dock-list {
  min-height: 0;
  overflow: hidden;
  max-height: 60vh;
  overflow-y: auto;
}

.queue-dock-option {
  display: block;
  width: 100%;
  padding: 7px 14px;
  border: none;
  border-left: 3px solid transparent;
  border-bottom: 1px solid var(--border-soft);
  background: var(--white);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease;
}

.queue-dock-option:hover {
  background: var(--sand-50);
}

.queue-dock-option[aria-selected='true'] {
  border-left-color: var(--pink-500);
  background: var(--pink-50);
}

/* An empty queue stays selectable but stops competing for attention. */
.queue-dock-option.is-empty {
  opacity: 0.55;
}

.queue-dock-option-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.queue-dock-option-label {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-strong);
}

/* One line each, so all nine queues fit without scrolling — a queue you have to
   scroll to find is a queue you forget exists. The full sentence still appears
   in the strip under the dock once a queue is selected, and in the title
   attribute on hover. */
.queue-dock-option-desc {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 1px;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--text-muted);
}

@media (prefers-reduced-motion: reduce) {
  .queue-dock-panel,
  .queue-dock-chevron {
    transition-duration: 0s;
  }
}

.queue-tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 17px;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  background: var(--sand-100);
  color: var(--sand-700);
  font-size: 10px;
  font-weight: 700;
}

.queue-tab-count[data-tone='danger'] {
  background: var(--danger-bg);
  color: var(--danger);
}

.queue-tab-count[data-tone='warning'] {
  background: var(--warning-bg);
  color: var(--warning);
}

.queue-tab-count[data-tone='pink'] {
  background: var(--pink-50);
  color: var(--pink-600);
}

/* =========================================================================
   Skeletons
   =========================================================================
   Shown while a panel's content is in flight. The panel itself opens on the
   click (Alpine), so without these the first thing a reviewer sees is an empty
   sheet — and on a slow connection, an empty sheet is indistinguishable from a
   broken one.

   Deliberately NOT a spinner: these mirror the shape of the content that is
   coming, so the layout does not jump when it lands, and the reviewer's eye is
   already where the campaign name will be.
   ========================================================================= */
.skeleton {
  background: linear-gradient(
    90deg,
    var(--sand-100) 0%,
    var(--sand-50) 40%,
    var(--sand-100) 80%
  );
  background-size: 300% 100%;
  border-radius: var(--radius-sm);
  animation: skeleton-sweep 1.4s ease-in-out infinite;
}

@keyframes skeleton-sweep {
  0% { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

.skeleton-row {
  height: 14px;
  margin-bottom: 10px;
}

.skeleton-card {
  height: 72px;
  margin-bottom: 12px;
}

.skeleton-block {
  height: 130px;
  margin-bottom: 16px;
}

.skeleton-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 10px;
  margin-bottom: 16px;
}

/* A stalled request should not leave a shimmer running forever; after a few
   seconds the sweep settles so the panel reads as "stuck", not "loading". */
@media (prefers-reduced-motion: reduce) {
  .skeleton {
    animation: none;
  }
}

/* Announced to screen readers, invisible on screen. The panel skeleton already
   communicates "loading" visually; this is the equivalent for anyone who cannot
   see it, and clip-path rather than display:none because a hidden element is
   not announced at all. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Story builder

   The impact story editor inside the CMS slide-over. Unlike the rest of the
   admin views — which carry their styling inline — this is a form dense enough
   that inline styles made the markup unreadable, and the tiles need :hover,
   :focus-visible and [draggable] states that inline styles cannot express.
   ═══════════════════════════════════════════════════════════════════════════ */

.builder {
  display: grid;
  /* Writing column wide enough for prose, image column wide enough for two
     gallery tiles side by side. */
  grid-template-columns: minmax(0, 1.35fr) minmax(300px, 1fr);
  gap: 28px;
  align-items: start;
}

@media (max-width: 900px) {
  .builder {
    grid-template-columns: minmax(0, 1fr);
    gap: 22px;
  }
}

.builder-col {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-width: 0;
}

.builder-section {
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: 18px 20px;
}

.builder-section-title {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 14px;
}

.builder-section-title svg {
  width: 13px;
  height: 13px;
}

.builder-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.builder-field + .builder-field {
  margin-top: 16px;
}

.builder-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-strong);
}

.builder-label .optional {
  font-weight: 500;
  color: var(--text-muted);
}

.builder-input,
.builder-select,
.builder-textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--white);
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--text-strong);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.builder-textarea {
  min-height: 220px;
  line-height: 1.6;
  resize: vertical;
}

.builder-input:focus,
.builder-select:focus,
.builder-textarea:focus {
  outline: none;
  border-color: var(--olive-500);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--olive-600) 22%, transparent);
}

.builder-input[aria-invalid='true'],
.builder-select[aria-invalid='true'],
.builder-textarea[aria-invalid='true'] {
  border-color: var(--danger);
}

.builder-help {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
}

.builder-error {
  font-size: 12px;
  font-weight: 500;
  color: var(--danger);
}

.builder-count {
  font-size: 11.5px;
  color: var(--text-muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* A control sitting inside a text field — currently the headline generator.
   The wrapper exists only to give the button something to position against;
   .builder-field is a flex column, so the button cannot anchor to it. */
.builder-input-wrap {
  position: relative;
  display: block;
}

/* Keeps typed text clear of the button. Matched to the button's width plus its
   inset, so a long headline scrolls under nothing. */
.builder-input--has-action {
  padding-right: 116px;
}

.builder-input-action {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--sand-50);
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  color: var(--text-strong);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.builder-input-action:hover:not(:disabled) {
  background: var(--olive-50);
  border-color: var(--olive-500);
  color: var(--olive-700);
}

.builder-input-action:focus-visible {
  outline: 2px solid var(--olive-600);
  outline-offset: 1px;
}

.builder-input-action:disabled {
  cursor: default;
  opacity: 0.65;
}

.builder-input-action-inner {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}

.builder-input-action-inner i,
.builder-input-action-inner svg {
  width: 13px;
  height: 13px;
}

/* Anchored vs standalone. A segmented control rather than two radios: the
   choice changes which fields the form shows, so it needs to read as a mode
   switch, not a checkbox. */
.builder-modes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

/* The field that follows the mode switch is not a .builder-field sibling of
   another one, so it misses the adjacent-sibling spacing rule above. */
.builder-modes + .builder-field {
  margin-top: 18px;
}

.builder-mode {
  position: relative;
  display: block;
  padding: 12px 14px;
  border: 1.5px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--white);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}

.builder-mode:hover {
  border-color: var(--sand-400);
  background: var(--sand-50);
}

.builder-mode input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.builder-mode:has(input:checked) {
  border-color: var(--olive-500);
  background: var(--olive-50);
}

.builder-mode:has(input:focus-visible) {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--olive-600) 22%, transparent);
}

.builder-mode-name {
  display: block;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text-strong);
  margin-bottom: 2px;
}

.builder-mode-hint {
  display: block;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-muted);
}

/* ── Cover image ─────────────────────────────────────────────────────────── */

.builder-cover {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--sand-100);
}

.builder-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Actions sit over the image, on a scrim so they stay legible over a light
   photo. */
.builder-cover-actions {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  padding: 10px;
  background: linear-gradient(to top, rgb(31 32 18 / 0.6), transparent);
}

.builder-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  aspect-ratio: 16 / 10;
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--sand-50);
  color: var(--text-muted);
  cursor: pointer;
  text-align: center;
  padding: 16px;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.builder-drop:hover,
.builder-drop:focus-within {
  border-color: var(--olive-500);
  background: var(--olive-50);
  color: var(--olive-700);
}

.builder-drop svg {
  width: 22px;
  height: 22px;
}

.builder-drop-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-strong);
}

.builder-drop-hint {
  font-size: 11.5px;
  line-height: 1.4;
}

.builder-drop input[type='file'] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* ── Gallery ─────────────────────────────────────────────────────────────── */

.builder-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 8px;
}

.builder-tile {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--sand-100);
  cursor: grab;
}

.builder-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}

.builder-tile:active {
  cursor: grabbing;
}

/* The tile being dragged. Dimmed so the gap it will leave is visible. */
.builder-tile[data-dragging='true'] {
  opacity: 0.35;
}

/* Where the drop will land. */
.builder-tile[data-drop-target='true']::after {
  content: '';
  position: absolute;
  inset: 0;
  border: 2px solid var(--olive-500);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--olive-500) 14%, transparent);
}

.builder-tile-bar {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 5px 4px;
  background: linear-gradient(to top, rgb(31 32 18 / 0.72), transparent);
  opacity: 0;
  transition: opacity 0.15s ease;
}

.builder-tile:hover .builder-tile-bar,
.builder-tile:focus-within .builder-tile-bar {
  opacity: 1;
}

/* Always visible on touch, where there is no hover to reveal them and no drag
   affordance either. */
@media (hover: none) {
  .builder-tile-bar {
    opacity: 1;
  }
}

.builder-tile-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: rgb(255 255 255 / 0.9);
  color: var(--sand-800);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.builder-tile-btn:hover {
  background: var(--white);
}

.builder-tile-btn:focus-visible {
  outline: 2px solid var(--white);
  outline-offset: 1px;
}

.builder-tile-btn[disabled] {
  opacity: 0.4;
  cursor: default;
}

.builder-tile-btn.is-danger:hover {
  background: var(--danger);
  color: var(--white);
}

/* The replace-cover control is a <label> wrapping a file input, so the input
   itself has to be taken out of the layout — otherwise the browser's "Choose
   file" widget renders inside the 24px button. */
.builder-tile-btn input[type='file'] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.builder-tile-btn:has(input[type='file']) {
  cursor: pointer;
}

.builder-tile-btn svg {
  width: 13px;
  height: 13px;
}

.builder-tile-add {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--sand-50);
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.builder-tile-add:hover,
.builder-tile-add:focus-within {
  border-color: var(--olive-500);
  background: var(--olive-50);
  color: var(--olive-700);
}

.builder-tile-add svg {
  width: 20px;
  height: 20px;
}

.builder-tile-add input[type='file'] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Upload in flight. */
.builder-uploading {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  font-size: 12px;
  color: var(--text-muted);
}

.builder-spinner {
  width: 13px;
  height: 13px;
  border: 2px solid var(--sand-300);
  border-top-color: var(--olive-600);
  border-radius: 50%;
  animation: builder-spin 0.7s linear infinite;
  flex-shrink: 0;
}

@keyframes builder-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .builder-spinner {
    animation-duration: 2s;
  }
}

/* ── Publish switch ──────────────────────────────────────────────────────── */

.builder-switch {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  cursor: pointer;
}

.builder-switch input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.builder-switch-track {
  position: relative;
  flex-shrink: 0;
  width: 38px;
  height: 22px;
  margin-top: 1px;
  border-radius: var(--radius-pill);
  background: var(--sand-300);
  transition: background 0.18s ease;
}

.builder-switch-track::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--white);
  box-shadow: var(--shadow-sm);
  transition: transform 0.18s var(--ease-panel-in);
}

.builder-switch:has(input:checked) .builder-switch-track {
  background: var(--success);
}

.builder-switch:has(input:checked) .builder-switch-track::after {
  transform: translateX(16px);
}

.builder-switch:has(input:focus-visible) .builder-switch-track {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--olive-600) 25%, transparent);
}

.builder-switch-text {
  min-width: 0;
}

.builder-switch-name {
  display: block;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text-strong);
}

.builder-switch-hint {
  display: block;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-muted);
  margin-top: 1px;
}

/* A checkbox that gates publishing, so it reads as a statement being made
   rather than a preference. */
.builder-check {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 11px 12px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--sand-50);
  cursor: pointer;
}

.builder-check input {
  margin: 1px 0 0;
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  accent-color: var(--olive-600);
}

.builder-check-text {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-body);
}

@media (prefers-reduced-motion: reduce) {
  .builder-switch-track,
  .builder-switch-track::after,
  .builder-tile-bar {
    transition-duration: 0s;
  }
}
