/*
 * Landing Page Editor — Chrome & Overlay Styles
 *
 * Extracted from design/edit.html. Contains only the editor shell styles
 * (edit toggle buttons, move controls, publish button, confirm bar).
 * Public landing page section appearance belongs in the public layout CSS.
 */

/* ─── Preview Scope Reset ─── */
/* Ensures site.css body-level styles apply to the editor preview area,
   overriding any admin dashboard defaults that bleed through. The root
   font-size reset (html { font-size: 16px }) lives in the editor layout
   inline style so rem units in site.css compute correctly. */
.landing-page-preview {
    font-size: 16px;
    line-height: 1.6;
    font-weight: 400;
    color: var(--dark-color);
}

/* ─── Admin Style Bleed Fixes ─── */
/* app/style.css sets h1-h3 { color: var(--primary-color) } for the admin
   dashboard. This overrides inherited colors in landing page sections
   (e.g., hero h1 should inherit white from .hero { color: white }).
   Resetting to inherit lets site.css section colors win. */
.landing-page-preview h1,
.landing-page-preview h2,
.landing-page-preview h3,
.landing-page-preview h4,
.landing-page-preview h5,
.landing-page-preview h6 {
    color: inherit;
    font-family: var(--heading-font);
    font-weight: 600;
    letter-spacing: normal;
}

/* site.css section-title sets its own color; this lets it through */
.landing-page-preview .section-title {
    color: var(--primary-color);
}

/* site.css card styles; admin Bootstrap overrides them */
.landing-page-preview .card-title {
    color: var(--primary-color);
    font-weight: 700;
    letter-spacing: 0.3px;
}

/* The buttons and the footer used to be re-declared here, as copies of
   site.css taken when the editor still loaded Bootstrap. Both copies outlived
   their reason and went stale, and this file is served *unlayered* while
   site.css's section rules sit in `@layer components` — so a copy wins over the
   original whatever its specificity, and nothing on either side can notice the
   drift.

   The footer copy is what MM-205 reported. It painted `var(--dark-color)`,
   reading that token by luminance when it is defined by role: `dark_color` is
   the *body text* colour, so under a dark theme the editor's footer became the
   lightest band on the page — white text on #ece7f2. MM-76 fixed exactly this
   in site.css by introducing `--footer-bg`; it could not fix a second copy it
   did not know about.

   The button copies had drifted on geometry rather than colour: 10px/25px
   padding against site.css's `rounded-full px-6 py-2.5`, so the same "Send
   Message" rendered square in the editor and as a pill on the public page.

   Nothing replaces them. site.css owns `footer`, `.btn`, `.btn-primary`,
   `.btn-outline-primary` and `.btn-lg` for both surfaces, which is the only
   arrangement in which they cannot disagree. */

/* ─── Shared Styles (no Bootstrap CDN) ───────────────────────────────────
   The Inertia editor layout no longer loads the Bootstrap CDN.
   Tailwind's Preflight reset strips heading sizes and list styles.
   These definitions restore Bootstrap-equivalent heading sizes for the
   preview, and form controls for the editor's own white sheets.

   The dividing line, learned the expensive way in MM-205: anything inside
   `.landing-page-preview` that a *visitor* also sees is site.css's, and a
   declaration here silently outranks it — this file is unlayered, site.css's
   section rules are in `@layer components`, and unlayered beats layered whatever
   the specificity. So a copy made here never loses and never reports. Restore
   something for the preview only when site.css genuinely has no rule for it.
   ─────────────────────────────────────────────────────────────────────── */

/* Heading sizes (Bootstrap defaults: h1=2.5rem, h2=2rem, h3=1.75rem, etc.) */
.landing-page-preview h1 { font-size: 2.5rem; }
.landing-page-preview h2 { font-size: 2rem; }
.landing-page-preview h3 { font-size: 1.75rem; }
.landing-page-preview h4 { font-size: 1.5rem; }
.landing-page-preview h5 { font-size: 1.25rem; }
.landing-page-preview h6 { font-size: 1rem; }

/* `.lead` is deliberately absent. HeroDisplay's `<p class="lead">` is the only
   element that carries it, and site.css's `.hero p` already sizes that — with a
   mobile step-down to 1rem this copy overrode, so the editor's hero paragraph
   stayed 1.25rem on a phone while the public page's shrank. */

/* Form controls — editor chrome only.
 *
 * Scoped to the two containers that are *white sheets by design*: the card
 * dialogs (`.offering-edit-modal`, applied by `CardDialog` to all four of them)
 * and the contact section's edit panel. Both keep a fixed light ground whatever
 * the theme, so they need fixed dark ink, and site.css's themed `.form-control`
 * — transparent, `var(--dark-color)` text — would be invisible on them under a
 * dark theme.
 *
 * It was previously scoped to `.landing-page-preview` as well, which is the
 * *preview*: the same markup a visitor gets. That put a hardcoded `#fff` input
 * with `#212529` text inside a contact card site.css had correctly painted
 * `var(--surface-color)`, and left the select's chevron at `%23343a40` — the
 * near-black-on-near-black case `lib/themeTokens.ts` documents and solves with
 * `--select-arrow`. MM-205. The preview's own form controls belong to site.css,
 * because they belong to the public page.
 */
.offering-edit-modal .form-control,
.contact-settings-panel .form-control { display: block; width: 100%; padding: 0.375rem 0.75rem; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; background-color: #fff; border: 1px solid #ced4da; border-radius: 0.375rem; appearance: none; transition: border-color .15s, box-shadow .15s; }
.offering-edit-modal .form-control:focus,
.contact-settings-panel .form-control:focus { border-color: #86b7fe; outline: 0; box-shadow: 0 0 0 0.25rem rgba(13,110,253,.25); }
.offering-edit-modal .form-select,
.contact-settings-panel .form-select { display: block; width: 100%; padding: 0.375rem 2.25rem 0.375rem 0.75rem; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; background-color: #fff; border: 1px solid #ced4da; border-radius: 0.375rem; appearance: none; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 16px 12px; }
.offering-edit-modal .form-label,
.contact-settings-panel .form-label { display: inline-block; margin-bottom: 0.5rem; font-weight: 500; }

/* ─── Editable Section Base ─── */
.editable-section {
    position: relative;
}

/* ─── Section Toolbar (edit / move up / move down) ─── */
/* One segmented control per editable section. Rounded corners live on the bar,
   not the segments, so the three buttons read as a single control. Boundary
   arrows are disabled rather than hidden — hiding one would resize the bar
   mid-interaction and shift the click target under the cursor. */
.section-toolbar {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 10;
    display: flex;
    align-items: stretch;
    border: 2px solid var(--primary-color);
    border-radius: 8px;
    background: white;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.section-toolbar-btn {
    width: 34px;
    height: 34px;
    border: none;
    background: transparent;
    color: var(--primary-color);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
    font-size: 0.95rem;
    transition: background-color 0.2s ease, color 0.2s ease;
}

.section-toolbar-btn + .section-toolbar-btn {
    border-left: 1px solid color-mix(in srgb, var(--primary-color) 30%, transparent);
}

.section-toolbar-btn svg {
    width: 17px;
    height: 17px;
}

.section-toolbar-btn:hover:not(:disabled) {
    background: var(--primary-color);
    color: white;
}

.section-toolbar-btn:disabled {
    opacity: 0.25;
    cursor: not-allowed;
}

.section-toolbar-btn.active {
    background: var(--accent-color);
    color: white;
}

/* ─── View / Edit Mode Visibility ─── */
.editable-section .view-mode {
    display: block;
}

.editable-section .edit-mode {
    display: none;
}

.editable-section.editing .view-mode {
    display: none;
}

.editable-section.editing .edit-mode {
    display: block;
}

/* ─── Hero Section Toolbar Override (white variant over dark background) ─── */
/* Load-bearing: the hero sits on a user-supplied background image, where the
   default primary-on-white toolbar has no reliable contrast. Without this the
   hero's controls do not break — they become invisible against a photo. */
.hero.editable-section .section-toolbar {
    top: 80px;
    border-color: white;
    background: rgba(255,255,255,0.15);
    backdrop-filter: blur(6px);
}

.hero.editable-section .section-toolbar-btn {
    color: white;
}

.hero.editable-section .section-toolbar-btn + .section-toolbar-btn {
    border-left-color: rgba(255,255,255,0.4);
}

.hero.editable-section .section-toolbar-btn:hover:not(:disabled) {
    background: white;
    color: var(--primary-color);
}

.hero.editable-section .section-toolbar-btn.active {
    background: rgba(255,255,255,0.9);
    color: var(--accent-color);
}

/* ─── Editor navbar action buttons (social-edit, theme-picker) ─── */
/* Match the existing nav-edit-btn / settings-btn pattern */
.social-edit-btn {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 1.5px solid var(--primary-color);
    background: white;
    color: var(--primary-color);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 0.7rem;
    transition: all 0.2s;
    flex-shrink: 0;
    margin-left: 8px;
}

.social-edit-btn:hover {
    background: var(--primary-color);
    color: white;
    transform: scale(1.1);
}

.theme-picker-btn {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 1.5px solid var(--primary-color);
    background: white;
    color: var(--primary-color);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-size: 0.75rem;
    transition: all 0.2s;
    flex-shrink: 0;
    margin-left: 6px;
}

.theme-picker-btn:hover {
    background: var(--primary-color);
    color: white;
    transform: scale(1.1);
}

/* ─── Publish Button (editor navbar) ─── */
.publish-site-btn {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 6px 16px;
    border-radius: 20px;
    border: none;
    font-family: 'Roboto Slab', serif;
    font-size: 0.8rem;
    font-weight: 700;
    cursor: pointer;
    transition: all 0.2s;
    white-space: nowrap;
    margin-left: 8px;
}

.publish-site-btn.unpublished {
    background: var(--primary-color);
    color: white;
    box-shadow: 0 2px 10px rgba(125,157,156,0.4);
}

.publish-site-btn.unpublished:hover {
    background: var(--accent-color);
    box-shadow: 0 4px 14px rgba(125,157,156,0.5);
    transform: translateY(-1px);
}

.publish-site-btn.published {
    background: #e8f5e9;
    color: #2e7d32;
    border: 1.5px solid #a5d6a7;
}

.publish-site-btn.published:hover {
    background: #ffebee;
    color: #c62828;
    border-color: #ef9a9a;
}

.publish-site-btn .status-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
    flex-shrink: 0;
}

/* ─── Publish Confirmation Bar (fixed bottom toast) ─── */
.publish-confirm-bar {
    display: none;
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    background: var(--dark-color);
    color: white;
    border-radius: 12px;
    padding: 14px 20px;
    z-index: 2000;
    align-items: center;
    gap: 16px;
    box-shadow: 0 8px 30px rgba(0,0,0,0.25);
    font-size: 0.88rem;
    white-space: nowrap;
}

.publish-confirm-bar.active {
    display: flex;
}

.publish-confirm-bar .confirm-yes {
    background: #c62828;
    color: white;
    border: none;
    border-radius: 8px;
    padding: 6px 16px;
    font-family: 'Roboto Slab', serif;
    font-size: 0.82rem;
    font-weight: 700;
    cursor: pointer;
}

.publish-confirm-bar .confirm-yes:hover {
    background: #b71c1c;
}

.publish-confirm-bar .confirm-no {
    background: rgba(255,255,255,0.15);
    color: white;
    border: 1px solid rgba(255,255,255,0.2);
    border-radius: 8px;
    padding: 6px 16px;
    font-family: 'Roboto Slab', serif;
    font-size: 0.82rem;
    cursor: pointer;
}

.publish-confirm-bar .confirm-no:hover {
    background: rgba(255,255,255,0.25);
}

/* ─── Shared Edit Form Styles (Phase 2) ─── */

.edit-mode-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(125, 157, 156, 0.1);
  border: 1px solid rgba(125, 157, 156, 0.3);
  color: var(--primary-color, #7d9d9c);
  padding: 4px 10px;
  border-radius: 6px;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 12px;
}

.btn-save {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 22px;
  background: var(--primary-color, #7d9d9c);
  color: white;
  border: 2px solid var(--primary-color, #7d9d9c);
  border-radius: 8px;
  font-family: "Roboto Slab", serif;
  font-weight: 700;
  font-size: 0.9rem;
  cursor: pointer;
  transition: all 0.2s;
}
.btn-save:hover {
  background: var(--accent-color, #5a7e7d);
  border-color: var(--accent-color, #5a7e7d);
}

.btn-cancel {
  display: inline-flex;
  align-items: center;
  padding: 10px 22px;
  background: transparent;
  color: var(--text-color, #333);
  border: 2px solid #dee2e6;
  border-radius: 8px;
  font-family: "Roboto Slab", serif;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  transition: all 0.2s;
}
.btn-cancel:hover {
  border-color: #adb5bd;
  background: #f8f9fa;
}

.about-image-upload {
  border-radius: 10px;
}
.about-upload-actions {
  margin-top: 12px;
  text-align: center;
}

.image-upload-wrapper {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  cursor: pointer;
}
.image-upload-wrapper:hover .image-upload-overlay {
  opacity: 1;
}
.image-upload-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
  opacity: 0;
  transition: opacity 0.3s;
  gap: 8px;
}
.image-upload-overlay i {
  font-size: 2rem;
}
.upload-hint {
  font-size: 0.8rem;
  opacity: 0.8;
}
.image-upload-input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
  width: 100%;
  height: 100%;
}

.edit-actions {
  display: flex;
  gap: 10px;
  margin-top: 16px;
  flex-wrap: wrap;
  align-items: center;
}

/* ─── About Edit-In-Place Styles ─── */

/* About is edit-in-place: the AboutGrid stays rendered across modes so the
   arrangement is visible while it is being changed. This bar sits outside the
   grid, so flipping the columns never moves the controls that do the flipping.
   Editor chrome only — the grid itself is styled by site.css. */
.about-edit-actions {
  display: flex;
  gap: 10px;
  margin: 24px auto 0;
  padding: 0 1rem;
  max-width: 80rem;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
}

.btn-flip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 22px;
  background: transparent;
  color: var(--primary-color, #7d9d9c);
  border: 2px solid var(--primary-color, #7d9d9c);
  border-radius: 8px;
  font-family: "Roboto Slab", serif;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  margin-right: auto;
  transition: background 0.2s ease, color 0.2s ease;
}
.btn-flip:hover {
  background: var(--primary-color, #7d9d9c);
  color: white;
}
.btn-flip:focus-visible {
  outline: 2px solid var(--primary-color, #7d9d9c);
  outline-offset: 2px;
}

/* About renders the chip under the image, so it needs the gap. Colour comes
   from the shared light-surface default further down. */
#about .image-uploader-modal-trigger {
  margin-top: 12px;
}

/* ─── Hero Edit-In-Place Styles ─── */

/* The hero section is itself the editable wrapper, so the bg image and
   50vh height survive across modes. Edit chrome is layered on top. */
.hero.editable-section.editing {
  /* `.hero` is a fixed 50vh in site.css, which the edit chrome cannot fit
     into on shorter viewports. Relaxing it to a minimum lets the section
     grow to its content instead of overflowing. This replaces a
     `padding-bottom: 100px` reservation for a floating action bar: that
     padding fought the section's own `items-center`, pinning content to
     the top edge (measured: overlay top 116.1 vs hero top 117) — the bar
     now sits in flow, so nothing needs reserving. */
  height: auto;
  min-height: 50vh;
}

/* Mode affordance. Without this the only cue that you are in edit mode is
   that the text became typeable, which is not discoverable. */
.hero-edit-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 14px;
  padding: 4px 12px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(6px);
  color: white;
  font-family: "Roboto Slab", serif;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.hero-edit-badge svg {
  width: 12px;
  height: 12px;
}

/* Inputs that visually mimic the rendered title and description.
   Transparent until hover/focus, with a dashed border that becomes
   visible on hover. `field-sizing: content` (modern browsers) lets the
   textareas auto-grow. */
.hero-title-inline,
.hero-subtitle-inline {
  /* The negative inline margin cancels this field's own padding + border so
     the *text* starts on the same x as the rendered <h1>/<p> (measured: 51 vs
     42 without it). The padding stays — the hover affordance needs room — it
     just bleeds outward instead of indenting the content.

     Derived from the two variables rather than hand-computed, so changing the
     padding or border keeps the alignment instead of silently breaking the one
     invariant this whole surface exists to hold. */
  --inline-pad-x: 8px;
  --inline-border: 1px;
  --inline-offset: calc(var(--inline-pad-x) + var(--inline-border));

  background: transparent;
  border: var(--inline-border) dashed transparent;
  color: inherit;
  font: inherit;
  resize: none;
  field-sizing: content;
  padding: 4px var(--inline-pad-x);
  margin: 0 calc(-1 * var(--inline-offset));
  width: calc(100% + 2 * var(--inline-offset));
  outline: none;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}
.hero-title-inline {
  font-family: "Roboto Slab", serif;
  font-weight: 700;
  font-size: 3.5rem;
  line-height: 1.1;
  margin-bottom: 20px;
}
.hero-subtitle-inline {
  font-size: 1.2rem;
  font-weight: 300;
  margin-bottom: 30px;
}
.hero-title-inline:hover,
.hero-subtitle-inline:hover {
  border-color: rgba(255, 255, 255, 0.4);
}
.hero-title-inline:focus,
.hero-subtitle-inline:focus {
  border-color: rgba(255, 255, 255, 0.7);
  background: rgba(255, 255, 255, 0.05);
}
.hero-title-inline::placeholder,
.hero-subtitle-inline::placeholder {
  color: rgba(255, 255, 255, 0.5);
}

/* CTA edit cells — buttons live as styled label inputs (live preview).
   Each cell sizes to its own text so the preview is the size of the real
   button; the URL editor is a popover (below) rather than a permanent
   field, because a field in the flow forced a fixed cell width and made
   every "button" roughly three times its rendered size. */
.hero-cta-edit-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 8px;
}
.hero-cta-edit-cell {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
}
.hero-cta-edit-label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1.5rem;
  border-radius: 6px;
  font-family: "Roboto Slab", serif;
  font-weight: 700;
  font-size: 1.1rem;
  cursor: text;
  outline: none;
  transition: filter 0.2s ease, box-shadow 0.2s ease;
  /* Match the rendered button's width. `field-sizing` is already relied on
     by the inline title/description textareas above; the min-width keeps an
     emptied label from collapsing to nothing and losing its click target. */
  field-sizing: content;
  min-width: 12ch;
  max-width: 100%;
  text-align: center;
}

/* URL editor for the focused CTA. Absolutely positioned so revealing it
   never resizes the button preview or reflows the row. */
.hero-cta-url-popover {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 6;
  min-width: 280px;
  padding: 10px 12px 12px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(20, 20, 20, 0.72);
  backdrop-filter: blur(10px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
}
.hero-cta-url-popover-label {
  display: block;
  margin-bottom: 6px;
  color: rgba(255, 255, 255, 0.75);
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.hero-cta-edit-label.is-primary {
  background: var(--primary-color, #7d9d9c);
  color: white;
  border: 2px solid var(--primary-color, #7d9d9c);
}
.hero-cta-edit-label.is-outline {
  background: transparent;
  color: white;
  border: 2px solid white;
}
.hero-cta-edit-label:focus {
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.25);
}

/* Action bar, in flow directly beneath the fields it applies to. It used to
   float at the hero's bottom-right, which detached it from the form and
   required the section to reserve 100px of bottom padding for it. */
.hero.editing .hero-edit-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 24px;
}

/* Compact chip-style trigger for ImageUploader in modal mode. Used by
   the hero "Replace image" action; doesn't show a thumbnail because the
   current image is already visible as the hero background. */
/* The default is the LIGHT-surface treatment, deliberately. This chip renders
   on three surfaces: the hero (dark image), the About section (white) and the
   mobile hero drawer (white, portalled to <body>). It used to default to
   white-on-translucent, so every light surface had to remember to re-tint it —
   About did, the drawer never did, and its "Change hero background" button was
   white on white and completely invisible (MM-73). Defaulting to the readable
   variant means a new light surface is safe without anyone remembering; only
   the one dark surface opts in below. */
.image-uploader-modal-trigger,
.image-uploader-framing-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0.5rem 1rem;
  border-radius: 6px;
  border: 2px solid var(--primary-color, #7d9d9c);
  background: transparent;
  color: var(--primary-color, #7d9d9c);
  font-family: "Roboto Slab", serif;
  font-weight: 600;
  font-size: 0.9rem;
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.image-uploader-modal-trigger:hover,
.image-uploader-framing-trigger:hover {
  background: var(--primary-color, #7d9d9c);
  border-color: var(--primary-color, #7d9d9c);
  color: white;
}
.image-uploader-modal-trigger:focus-visible,
.image-uploader-framing-trigger:focus-visible {
  outline: 2px solid var(--primary-color, #7d9d9c);
  outline-offset: 2px;
}

/* "Adjust framing" (add-image-focal-point) shares the trigger's rules above
   rather than copying them — a second copy under another name never loses
   and never reports. Only its spacing is its own. */
.image-uploader-framing-trigger {
  margin: 0.5rem 0 0 0.5rem;
}
.image-uploader-framing-trigger svg {
  width: 16px;
  height: 16px;
}

/* The hero is the one dark surface — it opts in to the glass treatment. The
   mobile drawer portals to <body>, so it correctly does not match this. */
.hero .image-uploader-modal-trigger,
.hero .image-uploader-framing-trigger {
  border-color: rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.12);
  color: white;
  backdrop-filter: blur(6px);
}
.hero .image-uploader-modal-trigger:hover,
.hero .image-uploader-framing-trigger:hover {
  background: rgba(255, 255, 255, 0.25);
  border-color: rgba(255, 255, 255, 0.7);
  color: white;
}
.hero .image-uploader-modal-trigger:focus-visible,
.hero .image-uploader-framing-trigger:focus-visible {
  outline: 2px solid white;
}

/* Override shared btn-save / btn-cancel for the dark hero background. */
.hero .btn-save {
  background: white;
  color: var(--accent-color, #5a7e7d);
  border-color: white;
}
.hero .btn-save:hover {
  background: #f0f4f4;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}
.hero .btn-cancel {
  border-color: rgba(255, 255, 255, 0.4);
  color: white;
  background: transparent;
}
.hero .btn-cancel:hover {
  border-color: white;
  background: rgba(255, 255, 255, 0.1);
}

/* CTA URL combobox styling. Uses neutral colors so it works inside both
   the hero overlay (dark bg) and the mobile drawer (light bg). The hero
   overlay variant is scoped under .hero. */
.cta-url-combobox {
  position: relative;
  width: 100%;
}
.cta-url-combobox-row {
  display: flex;
  align-items: stretch;
  gap: 4px;
}
.cta-url-combobox-input {
  flex: 1;
  padding: 6px 10px;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  font-size: 0.875rem;
  outline: none;
  background: white;
  color: #111827;
}
.cta-url-combobox-input:focus {
  border-color: var(--primary-color, #7d9d9c);
  box-shadow: 0 0 0 3px rgba(125, 157, 156, 0.2);
}
.cta-url-combobox-toggle {
  width: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  background: white;
  cursor: pointer;
  color: #6b7280;
}
.cta-url-combobox-toggle:hover {
  background: #f3f4f6;
}
.cta-url-combobox-listbox {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin: 4px 0 0;
  padding: 4px 0;
  list-style: none;
  background: white;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
  z-index: 20;
  max-height: 240px;
  overflow-y: auto;
}
.cta-url-combobox-option {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 12px;
  cursor: pointer;
  font-size: 0.875rem;
  color: #111827;
}
.cta-url-combobox-option.is-highlighted,
.cta-url-combobox-option:hover {
  background: #f3f4f6;
}
.cta-url-combobox-option-label {
  font-weight: 500;
}
.cta-url-combobox-option-href {
  font-size: 0.75rem;
  color: #6b7280;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
/* Neutral by default: this hint also renders in the mobile drawer, which is
   a light surface. It used to be hard-coded white and was invisible there.
   `margin-bottom` must be stated: this is a <p>, and inside the hero the
   site stylesheet's `.hero p { margin-bottom: 2rem }` otherwise adds 32px
   of dead space that pushes the popover out through the section's edge. */
.cta-url-combobox-hint {
  margin-top: 4px;
  margin-bottom: 0;
  font-size: 0.75rem;
  color: #6b7280;
}

/* Hero variant — the combobox sits in a dark glass popover over the hero
   image, where the default light form controls read as pasted on. This is
   the scoping the comment above has always claimed and never had. */
.hero .cta-url-combobox-input,
.hero .cta-url-combobox-toggle {
  border-color: rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.12);
  color: white;
}
.hero .cta-url-combobox-input::placeholder {
  color: rgba(255, 255, 255, 0.5);
}
.hero .cta-url-combobox-input:focus {
  border-color: rgba(255, 255, 255, 0.75);
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.15);
}
.hero .cta-url-combobox-toggle:hover {
  background: rgba(255, 255, 255, 0.25);
}
.hero .cta-url-combobox-listbox {
  background: rgba(28, 28, 28, 0.96);
  border-color: rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(10px);
}
.hero .cta-url-combobox-option {
  color: rgba(255, 255, 255, 0.92);
}
.hero .cta-url-combobox-option.is-highlighted,
.hero .cta-url-combobox-option:hover {
  background: rgba(255, 255, 255, 0.14);
}
.hero .cta-url-combobox-option-href {
  color: rgba(255, 255, 255, 0.55);
}
.hero .cta-url-combobox-hint {
  color: rgba(255, 255, 255, 0.7);
}

/* Mobile drawer — the surface for editing on viewports < 768px. Uses
   the shared shadcn Sheet primitive; this only adds drawer-specific
   tweaks for our content layout. */
.hero-edit-drawer .field-error {
  color: #b91c1c;
  font-size: 0.75rem;
  margin-top: 2px;
}

@media (max-width: 767px) {
  .hero-title-inline {
    font-size: 2rem;
  }
  .hero-subtitle-inline {
    font-size: 1rem;
  }
}

/* ─── Editable Section Title ─── */
.section-title-editable {
  display: none;
  width: 100%;
  max-width: 500px;
  padding: 8px 16px;
  border: 2px solid #ddd;
  border-radius: 8px;
  font-family: "Roboto Slab", serif;
  font-weight: 700;
  font-size: inherit;
  color: var(--primary-color);
  background: white;
  margin-bottom: 50px;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.section-title-editable:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 4px rgba(125, 157, 156, 0.15);
}

.editable-section.editing .section-title-view { display: none; }
.editable-section.editing .section-title-editable { display: block; }

.section-edit-controls {
  display: flex; align-items: center; gap: 12px; margin-bottom: 24px; flex-wrap: wrap;
}
.section-edit-controls .section-title-editable {
  display: block; margin-bottom: 0; flex: 1; min-width: 200px;
}

.btn-done-editing {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 20px; border: 2px solid var(--primary-color, #7c3aed);
  border-radius: 8px; background: white; color: var(--primary-color, #7c3aed);
  font-weight: 600; font-size: 0.9rem; cursor: pointer;
  transition: all 0.2s ease; white-space: nowrap;
}
.btn-done-editing:hover {
  background: var(--primary-color, #7c3aed); color: white;
}

/* ─── Classes Section ─── */

.class-card-wrapper { position: relative; transition: transform 0.25s ease, opacity 0.25s ease; }
.class-card-wrapper.dragging { opacity: 0.5; transform: scale(0.95); }
.class-card-wrapper.drag-over { transform: scale(1.02); }

.card-edit-controls {
  position: absolute; top: 0; left: 0; right: 0; z-index: 5;
  display: none; justify-content: space-between; align-items: flex-start;
  padding: 10px; pointer-events: none;
}
.editable-section.editing .card-edit-controls { display: flex; }
.card-edit-controls > * { pointer-events: auto; }

.drag-handle {
  width: 36px; height: 36px; border-radius: 8px; background: white;
  border: none; color: var(--dark-color, #333); display: flex;
  align-items: center; justify-content: center; cursor: grab;
  font-size: 1rem; box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: all 0.2s ease;
}
.drag-handle:hover { background: var(--primary-color, #7c3aed); color: white; transform: scale(1.1); }
.drag-handle:active { cursor: grabbing; }

.card-action-btns { display: flex; gap: 6px; }
.card-action-btn {
  width: 36px; height: 36px; border-radius: 8px; border: none;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; font-size: 0.9rem;
  box-shadow: 0 2px 8px rgba(0,0,0,0.15); transition: all 0.2s ease;
}
.card-action-btn:hover { transform: scale(1.1); }
.card-action-btn.edit-btn { background: white; color: var(--primary-color, #7c3aed); }
.card-action-btn.edit-btn:hover { background: var(--primary-color, #7c3aed); color: white; }
.card-action-btn.delete-btn { background: white; color: #dc3545; }
.card-action-btn.delete-btn:hover { background: #dc3545; color: white; }
.card-action-btn.publish-btn { background: white; color: #28a745; }
.card-action-btn.publish-btn.unpublished { color: #6c757d; }
.card-action-btn.publish-btn:hover { background: #28a745; color: white; }

.class-draft-badge {
  position: absolute; top: 10px; left: 10px; z-index: 4;
  background: #ffc107; color: #333; padding: 2px 10px;
  border-radius: 20px; font-size: 0.7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.05em;
  display: none;
}

.editable-section.editing .class-card-wrapper.unpublished .class-draft-badge {
  display: block;
}

/* Dim the day-pills in edit mode so they don't clash with controls */
.editable-section.editing .day-pills {
  opacity: 0;
  pointer-events: none;
}

/* Days checkbox pill grid — used in class edit modal */
.days-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.day-checkbox { display: none; }
.day-checkbox-label {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 36px; border-radius: 18px;
  border: 2px solid #ddd; font-size: 0.8rem; font-weight: 600;
  cursor: pointer; transition: all 0.2s ease;
  color: var(--dark-color, #333); user-select: none;
}
.day-checkbox:checked + .day-checkbox-label {
  background: var(--primary-color, #7c3aed);
  border-color: var(--primary-color, #7c3aed);
  color: white;
}
.day-checkbox-label:hover { border-color: var(--primary-color, #7c3aed); }

.add-class-card,
.add-event-card,
.add-testimonial-card {
  display: none; flex-direction: column; align-items: center; justify-content: center;
  height: 100%; min-height: 200px; border: 2px dashed var(--primary-color, #7c3aed);
  border-radius: 10px; cursor: pointer; color: var(--primary-color, #7c3aed);
  transition: all 0.2s ease; gap: 10px; padding: 20px;
  background: transparent;
}
.editable-section.editing .add-class-card,
.editable-section.editing .add-event-card,
.editable-section.editing .add-testimonial-card { display: flex; }
.add-class-card:hover,
.add-event-card:hover,
.add-testimonial-card:hover { background: rgba(124,58,237,0.05); transform: scale(1.02); }
.add-class-card i,
.add-event-card i,
.add-testimonial-card i { font-size: 2rem; }
.add-class-card span,
.add-event-card span,
.add-testimonial-card span { font-size: 0.9rem; font-weight: 600; }

/* Modal overlay — per-entity backdrop classes avoid Bootstrap class collisions */
.class-edit-modal-backdrop,
.testimonial-modal-backdrop,
.offering-edit-modal-backdrop {
  position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 1050;
  background: rgba(0,0,0,0.5); display: flex;
  align-items: center; justify-content: center;
}
.class-edit-modal,
.testimonial-modal,
.offering-edit-modal {
  background: white;
  border-radius: 14px;
  padding: 36px;
  width: 90%;
  max-width: 580px;
  max-height: 90vh;
  overflow-y: auto;
  position: fixed;
  inset: 0;
  margin: auto;
  height: fit-content;
  z-index: 1051;
  box-shadow: 0 20px 60px rgba(0,0,0,0.25);
  animation: modalSlideIn 0.25s ease;
}
.offering-edit-modal { max-width: 520px; }
.testimonial-modal  { max-width: 520px; }
@keyframes modalSlideIn {
  from { opacity: 0; transform: translateY(20px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0)    scale(1); }
}
.class-edit-modal h3,
.testimonial-modal h3,
.offering-edit-modal h3 {
  font-family: "Roboto Slab", serif;
  font-weight: 700;
  color: var(--primary-color, #7d9d9c);
  margin-bottom: 24px;
  font-size: 1.4rem;
}
.class-edit-modal .form-label,
.testimonial-modal .form-label,
.offering-edit-modal .form-label {
  font-weight: 600;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--accent-color, #5a7e7d);
  margin-bottom: 6px;
}
.class-edit-modal .form-control,
.class-edit-modal .form-select,
.testimonial-modal .form-control,
.testimonial-modal .form-select,
.offering-edit-modal .form-control,
.offering-edit-modal .form-select {
  font-family: "Roboto Slab", serif;
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  padding: 10px 14px;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.class-edit-modal .form-control:focus,
.class-edit-modal .form-select:focus,
.testimonial-modal .form-control:focus,
.testimonial-modal .form-select:focus,
.offering-edit-modal .form-control:focus,
.offering-edit-modal .form-select:focus {
  border-color: var(--primary-color, #7d9d9c);
  box-shadow: 0 0 0 3px rgba(125,157,156,0.15);
}
.class-edit-modal textarea.form-control,
.testimonial-modal textarea.form-control,
.offering-edit-modal textarea.form-control {
  min-height: 90px;
  resize: vertical;
}
.modal-close-btn {
  position: absolute; top: 16px; right: 16px; background: var(--light-color, #f5f5f5); border: none;
  font-size: 0.9rem; cursor: pointer; color: var(--dark-color, #333); width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center; border-radius: 50%;
  transition: background 0.2s;
}
.modal-close-btn:hover { background: #ddd; }
.publish-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 4px;
}
.publish-toggle-note {
  font-size: 0.8rem;
  color: #6b7280;
  margin-top: 2px;
}
/* Shown only when the event slider is turned off on an event whose detail page
   is published — that unpublish clears detail_page_published_at and
   re-publishing does not restore it. */
.publish-toggle-warning {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 10px;
  padding: 10px 12px;
  border: 1px solid #fcd34d;
  border-radius: 8px;
  background: #fffbeb;
  color: #92400e;
  font-size: 0.8rem;
  line-height: 1.4;
}
.publish-toggle-warning svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  margin-top: 1px;
}
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 28px; }
body.modal-open { overflow: hidden; }

/* ─── Events Section ─── */
.event-type-badge {
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
  padding: 3px 10px; border-radius: 20px; letter-spacing: 0.05em;
}
.event-type-workshop { background: #6f42c1; color: white; }
.event-type-retreat   { background: #20c997; color: white; }
.event-type-training  { background: #fd7e14; color: white; }
.event-type-event     { background: #0d6efd; color: white; }

/* Event type selector in modal */
.event-type-selector {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.event-type-option {
    display: none;
}

.event-type-option-label {
    padding: 6px 16px;
    border: 2px solid #ddd;
    border-radius: 20px;
    cursor: pointer;
    font-size: 0.85rem;
    font-weight: 600;
    transition: all 0.2s ease;
    color: var(--dark-color, #333);
}

.event-type-option:checked + .event-type-option-label {
    background: var(--primary-color, #7c3aed);
    border-color: var(--primary-color, #7c3aed);
    color: white;
}

.event-type-option-label:hover {
    border-color: var(--primary-color, #7c3aed);
}

/* ============================================================
   OFFERINGS SECTION
   ============================================================ */

/* Layout, the card itself and everything printed on it now live in `site.css`
   (MM-207). They are markup a visitor also gets -- `OfferingCardDisplay` is
   rendered by both the public page and the editor -- and they were only ever
   here, so the public offerings section had no card and no grid at all.

   What remains below is chrome: the drag/publish/delete controls, the draft
   badge and the add tile, none of which a visitor ever sees. Keep it that way.
   A copy of a `site.css` rule taken back into this file would not merely
   duplicate it, it would outrank it -- this stylesheet is served raw by
   Propshaft and is therefore unlayered, while site.css's section rules sit in
   `@layer components`. */

.offering-card-wrapper {
  position: relative;
}

/* Editor-only state: an unpublished offering, dimmed in the preview. On the
   wrapper rather than the card, because the card is now `site.css`'s and the
   editor has no second element of its own to hang this on. */
.offering-card-wrapper.draft-card {
  opacity: 0.6;
}

.offering-draft-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  background: #ffc107;
  color: #333;
  font-size: 0.65rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 4px;
  text-transform: uppercase;
  display: none;
}

.editable-section.editing .offering-card-wrapper.draft-card .offering-draft-badge {
  display: block;
}

/* Edit controls inside the card (absolute top-right) */
.offering-card-edit-controls {
  position: absolute;
  top: 10px;
  right: 10px;
  display: none;
  gap: 4px;
  z-index: 5;
}

.editable-section.editing .offering-card-wrapper .offering-card-edit-controls {
  display: flex;
}

/* These sit *on* the card, which now follows the theme, so they follow it too:
   `--surface-color` for the ground and `--primary-color` for the glyph, which
   is what the accent on a raised surface is elsewhere in site.css.
   They were `background: white` with `var(--bs-primary, #6366f1)` ink -- a
   fixed ground beneath themed siblings, inked with a Bootstrap variable this
   app has not defined since the Inertia migration, so it always painted that
   indigo fallback whatever theme the teacher had chosen. */
.offering-card-edit-controls button {
  width: 30px;
  height: 30px;
  border-radius: 6px;
  border: 1px solid rgba(125, 157, 156, 0.3);
  background: var(--surface-color, #fff);
  color: var(--primary-color, #7d9d9c);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  transition: all 0.2s;
}

.offering-card-edit-controls button:hover {
  background: var(--primary-color, #7d9d9c);
  color: var(--surface-color, #fff);
  border-color: var(--primary-color, #7d9d9c);
}

.offering-card-edit-controls button.delete-btn:hover {
  background: #dc3545;
  border-color: #dc3545;
  color: #fff;
}

.add-offering-card {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 200px;
  border: 2px dashed rgba(125, 157, 156, 0.4);
  border-radius: 14px;
  cursor: pointer;
  color: var(--primary-color, #7d9d9c);
  font-size: 0.9rem;
  gap: 10px;
  transition: border-color 0.2s, background 0.2s;
  background: transparent;
}

.add-offering-card:hover {
  border-color: var(--primary-color, #7d9d9c);
  background: rgba(125, 157, 156, 0.05);
}

.add-offering-card i {
  font-size: 1.8rem;
}

.add-offering-card span {
  font-size: 0.9rem;
  font-weight: 500;
}

/* Show add card only in editing mode */
.editable-section.editing .add-offering-card {
  display: flex;
}

/* Icon picker grid */
.icon-picker-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.icon-picker-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 0.25rem;
  border: 2px solid #e2e8f0;
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
  font-size: 0.7rem;
  color: #64748b;
  gap: 0.25rem;
  transition: border-color 0.15s, background 0.15s;
}

/* `svg`, not `i`: these icons are lucide components. The `i` selectors this
   replaces had matched nothing since that migration, so the picker's icons
   silently took the button's own colour -- which looked deliberate, and is why
   nobody noticed (MM-207). Sized explicitly because lucide defaults to 24px,
   nearly twice this button's 0.7rem label. */
.icon-picker-btn svg {
  width: 1.25rem;
  height: 1.25rem;
  color: #475569;
}

/* The picker sits inside `.offering-edit-modal`, a deliberately white dialog,
   so its ground stays fixed -- but the *selection* tint has to be the site's
   accent or it says nothing about the site. It was `#ede9fe`, the lavender that
   goes with `--bs-primary`'s indigo fallback, which clashed the moment the
   border and label became theme colours. Mixed against white rather than given
   an alpha because a token cannot be unpacked into `rgba()`. */
.icon-picker-btn.selected {
  border-color: var(--primary-color, #7d9d9c);
  background: color-mix(in srgb, var(--primary-color, #7d9d9c) 12%, #fff);
  color: var(--primary-color, #7d9d9c);
}

.icon-picker-btn.selected svg {
  color: var(--primary-color, #7d9d9c);
}

/* Feature list editor */
.offering-features-editor {
  margin-bottom: 0.5rem;
}

.add-feature-btn {
  background: none;
  border: 1px dashed #94a3b8;
  border-radius: 6px;
  padding: 0.25rem 0.75rem;
  font-size: 0.825rem;
  color: #64748b;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}

.add-feature-btn:hover {
  border-color: var(--primary-color, #7d9d9c);
  color: var(--primary-color, #7d9d9c);
}

/* Offering edit modal sizing override */
.offering-edit-modal {
  max-width: 560px;
}

/* ============================================================
   TESTIMONIALS SECTION
   ============================================================ */

/* View mode: carousel — hidden when .editing */
.editable-section .testimonials-carousel-view {
  display: block;
}

.editable-section.editing .testimonials-carousel-view {
  display: none;
}

/* Also hide by ID when views use #testimonials-carousel-wrapper */
.testimonials.editing #testimonials-carousel-wrapper {
  display: none;
}

/* Edit mode: list — hidden when not .editing */
.editable-section .testimonials-edit-list {
  display: none;
}

.editable-section.editing .testimonials-edit-list {
  display: block;
}

/* site.css testimonial styles; admin Bootstrap overrides them in the editor */
.landing-page-preview .testimonial-text {
    font-style: italic;
    color: var(--dark-color);
    font-weight: 300;
}

.landing-page-preview .author-info h5 {
    color: var(--primary-color);
    font-weight: 600;
}

.landing-page-preview .testimonial-avatar-initials {
    background-color: var(--primary-color);
    border-color: var(--primary-color);
}

/* No `.testimonial-rating` override here. It used to restate site.css's
   hardcoded `#b08fd1`, and the only thing two copies of a literal can do is
   disagree; site.css now resolves that colour from `--primary-color`, which
   this surface sets identically (MM-206). Re-adding a copy would reintroduce
   exactly the editor/public drift rule 4 in CLAUDE.md exists to prevent. */

/* Testimonial edit card (card-based layout)
 *
 * This is the surface MM-207 was reported against. It was a fixed `white` sheet
 * whose contents read theme tokens -- `.edit-quote` is `var(--dark-color)`, the
 * *body text* colour, which a dark theme resolves to near-white. Measured
 * before the fix: 1.16:1, effectively invisible. It is the same defect MM-205
 * found on `.contact-settings-panel` and stated as a rule in CLAUDE.md: a
 * chrome surface that opts out of the theme must opt out of its tokens too.
 *
 * Resolved the other way round here, deliberately. The panel is a *dialog* over
 * the page, and a white sheet is what a dialog is; this card is the testimonial
 * itself, standing where the rendered one stands, inside a section the theme
 * has already painted. So it takes the theme instead -- `--surface-color` and
 * `--card-border`, the pairing site.css gives every other raised card -- and
 * the tokens its ink was already reading now resolve against a ground from the
 * same system. Fixing the ink instead would have left a white rectangle in the
 * middle of a dark page and called it correct. */
.testimonial-edit-card {
  background: var(--surface-color, #fff);
  border: 1px solid var(--card-border, transparent);
  border-radius: 10px;
  padding: 24px;
  position: relative;
  margin-bottom: 16px;
  box-shadow: 0 3px 12px rgba(0,0,0,0.06);
  display: flex;
  gap: 16px;
  align-items: flex-start;
  transition: box-shadow 0.2s;
}
.testimonial-edit-card:hover {
  box-shadow: 0 5px 20px rgba(0,0,0,0.1);
}
.testimonial-edit-card.unpublished {
  opacity: 0.55;
  border-left: 4px solid #ccc;
}
.testimonial-edit-card.dragging {
  opacity: 0.5;
}
.testimonial-edit-card.drag-over {
  border-top: 3px solid var(--primary-color, #6366f1);
}

/* Avatar in edit card */
.testimonial-edit-avatar {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  flex-shrink: 0;
  overflow: hidden;
  border: 2px solid var(--primary-color, #7d9d9c);
}
.testimonial-edit-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.testimonial-edit-avatar-initials {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--primary-color, #7d9d9c);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 1rem;
  border: 2px solid var(--primary-color, #7d9d9c);
}

/* Body content */
.testimonial-edit-body {
  flex: 1;
  min-width: 0;
}
.testimonial-edit-body h5 {
  font-weight: 600;
  color: var(--primary-color, #7d9d9c);
  margin-bottom: 2px;
  font-size: 1rem;
}
.testimonial-edit-body .edit-meta {
  font-size: 0.82rem;
  color: var(--accent-color, #6b7280);
  margin-bottom: 6px;
}

/* Stars inline in the meta line (MM-206).
 *
 * Tailwind's preflight sets `svg { display: block }`, so the five stars this
 * line used to hold as bare siblings rendered as five stacked lines. The public
 * card was never affected only because `.testimonial-rating` is `flex`, which
 * re-establishes the same SVGs as flex items — identical markup, opposite
 * outcomes, decided by the container.
 *
 * `inline-flex` on a wrapper rather than `flex` on `.edit-meta` itself: the
 * meta text and the `·` separator are bare text nodes, and making the parent a
 * flex container would turn each into an anonymous flex item with its own
 * whitespace handling. This keeps the row as one atom inside ordinary text.
 *
 * The size is explicit because lucide defaults to 24px, which is nearly three
 * times this line's 0.82rem — the stars dominated the card even before they
 * wrapped.
 */
.testimonial-edit-body .testimonial-edit-rating {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  vertical-align: text-bottom;
  color: var(--primary-color, #7d9d9c);
}
.testimonial-edit-body .testimonial-edit-rating svg {
  width: 14px;
  height: 14px;
}
.testimonial-edit-body .edit-quote {
  font-size: 0.9rem;
  color: var(--dark-color, #374151);
  font-style: italic;
  line-height: 1.5;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Status badge */
.testimonial-edit-status {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 3px 8px;
  border-radius: 4px;
  margin-top: 6px;
  display: inline-block;
}
/* A semantic green on a ground the theme now owns. `#198754` was fixed, so it
   held its contrast on the old white card and lost it once the card could be
   dark -- measured at 3.32:1. Mixing the hue *toward* `--dark-color` keeps it
   green while pulling it to whichever end of the range is legible: on a light
   theme that token is near-black and the green darkens, on a dark one it is
   near-white and the green lifts. One declaration, both polarities, and the
   colour still says "published". */
.testimonial-edit-status.published {
  background: color-mix(in srgb, #198754 12%, var(--surface-color, #fff));
  color: color-mix(in srgb, #198754 62%, var(--dark-color, #198754));
}
/* "Draft" and "unpublished" are *quiet* states, not grey ones. A literal
   mid-grey is the single value that reads badly against both polarities, which
   is exactly what these were; muting the card's own ink instead keeps them
   quiet on either. `--dark-color` is the body-text token, so this is the same
   ink at lower emphasis rather than a fourth colour (MM-207). */
.testimonial-edit-status.draft {
  background: color-mix(in srgb, var(--dark-color, #374151) 8%, var(--surface-color, #fff));
  color: color-mix(in srgb, var(--dark-color, #374151) 70%, var(--surface-color, #fff));
}

/* Action buttons — vertical stack */
.testimonial-edit-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex-shrink: 0;
}
.testimonial-edit-actions button,
.testimonial-edit-actions .pub-btn,
.testimonial-edit-actions .del-btn {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 0.85rem;
  transition: all 0.2s;
  /* `--light-color` is the *section* ground, so on the themed card above these
     buttons now read as holes punched through it. A mix against the card keeps
     them a step off their own surface on either polarity. */
  background: color-mix(in srgb, var(--dark-color, #374151) 8%, var(--surface-color, #f1f5f9));
  color: var(--dark-color, #374151);
}
.testimonial-edit-actions button:hover {
  transform: scale(1.1);
}
.testimonial-edit-actions .pub-btn.published {
  background: color-mix(in srgb, #198754 14%, var(--surface-color, #fff));
  color: color-mix(in srgb, #198754 62%, var(--dark-color, #198754));
}
.testimonial-edit-actions .pub-btn.unpublished {
  background: color-mix(in srgb, var(--dark-color, #374151) 8%, var(--surface-color, #fff));
  color: color-mix(in srgb, var(--dark-color, #374151) 65%, var(--surface-color, #fff));
}
.testimonial-edit-actions .del-btn:hover {
  background: #fce4e4;
  color: #dc3545;
}

/* Delete confirmation overlay */
.delete-confirm {
  position: absolute;
  inset: 0;
  background: rgba(220, 53, 69, 0.92);
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 10;
  color: white;
}
.delete-confirm p {
  font-weight: 600;
  margin-bottom: 12px;
  font-size: 1rem;
}
.delete-confirm-actions {
  display: flex;
  gap: 10px;
}
.delete-confirm-actions .confirm-yes-form button {
  background: white;
  color: #dc3545;
  border: none;
  padding: 6px 18px;
  border-radius: 6px;
  font-weight: 600;
  cursor: pointer;
}
.delete-confirm-actions .confirm-no {
  background: transparent;
  color: white;
  border: 2px solid white;
  padding: 6px 18px;
  border-radius: 6px;
  font-weight: 600;
  cursor: pointer;
}

/* Card image upload area styles live in app/javascript/styles/image_uploader.css
   (loaded via the Vite-bundled application.css for both editor and onboarding). */

/* Delete confirmation overlay */
.delete-confirm {
  position: absolute;
  inset: 0;
  background: rgba(220, 53, 69, 0.92);
  border-radius: 10px;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  color: white;
  z-index: 6;
  backdrop-filter: blur(2px);
}
.delete-confirm.visible { display: flex; }
.delete-confirm p {
  font-weight: 600;
  font-size: 1rem;
  margin: 0;
}
.delete-confirm-actions { display: flex; gap: 10px; }
.delete-confirm-actions button,
.delete-confirm-actions .confirm-yes-form button {
  padding: 8px 20px;
  border-radius: 8px;
  border: 2px solid white;
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
  transition: all 0.2s;
  background: transparent;
  color: white;
}
.delete-confirm-actions .confirm-yes-form button {
  background: white;
  color: #dc3545;
}
.delete-confirm-actions .confirm-no:hover { background: rgba(255,255,255,0.15); }

/* In-flight delete: DeleteConfirmOverlay sets `disabled` on both buttons while
   the request is running. Without this the pending state rendered identically
   to the idle one and the teacher got no feedback that anything was happening. */
.delete-confirm-actions button:disabled,
.delete-confirm-actions .confirm-yes-form button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.delete-confirm-actions button:disabled:hover,
.delete-confirm-actions .confirm-yes-form button:disabled:hover {
  background: transparent;
}
.delete-confirm-actions .confirm-yes-form button:disabled:hover {
  background: white;
}
/* Star rating picker in the testimonial modal (MM-206).
 *
 * These are the classes the React picker carries. What used to be here styled
 * `.star-rating-picker` — the reversed-radio ERB widget deleted in MM-202 —
 * so the stylesheet held rules matching nothing while the live markup had no
 * rules at all, and the picker's colour and size lived in inline styles that
 * no theme could reach. */
.star-rating-selector {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.star-rating-btn {
  background: none;
  border: none;
  padding: 0;
  /* An inline-block button reserves descender space below its SVG, which
     offsets the row against the Clear link sitting beside it. */
  line-height: 0;
  cursor: pointer;
  color: #d1d5db;
  transition: color 0.1s;
}

.star-rating-btn svg {
  width: 20px;
  height: 20px;
}

.star-rating-btn[aria-pressed="true"] {
  color: var(--primary-color, #7d9d9c);
}

/* Hover preview: the pointed-at star and everything left of it.
 *
 * The reset is not redundant. Without it, hovering star 2 of a 5-star rating
 * leaves 3–5 lit by `[aria-pressed="true"]`, so the preview shows the rating
 * the teacher already has rather than the one she is about to pick. Resetting
 * under `:hover` first and re-lighting is what makes the preview honest.
 *
 * `:has(~ …)` selects *preceding* siblings — a sibling combinator alone only
 * reaches later ones, which is why the deleted rules needed `row-reverse` to
 * work at all. */
.star-rating-selector:hover .star-rating-btn {
  color: #d1d5db;
}

.star-rating-selector:hover .star-rating-btn:hover {
  color: var(--primary-color, #7d9d9c);
}

/* Deliberately its own rule rather than grouped with the selector above. An
   unparseable selector invalidates the whole comma-separated list, so a browser
   without `:has()` would drop the plain `:hover` highlight with it and leave the
   row uniformly grey while the pointer is over it — worse than the no-op this
   split degrades to. */
.star-rating-selector:hover .star-rating-btn:has(~ .star-rating-btn:hover) {
  color: var(--primary-color, #7d9d9c);
}

/* Testimonial edit modal sizing */
.testimonial-edit-modal {
  max-width: 520px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
}

/* ============================================================
   CONTACT SECTION
   ============================================================ */

.contact-info-preview .col-md-6,
.contact-info-preview .col-12 {
  font-size: 0.95rem;
  color: #374151;
}

.contact-info-preview i {
  width: 1.25rem;
  text-align: center;
}

/* ── Contact Section ── */
.contact-settings-panel {
    background: white;
    padding: 32px 40px;
    border-radius: 10px;
    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
    height: 100%;
}

/* Fixed ink and a fixed rule, because the panel's ground is fixed.
   `--dark-color` and `--light-color` are defined by *role* — body text and
   section ground — so both inverted under the dark collection: the heading went
   near-white on this white sheet (a contrast ratio of about 1.1) and the rule
   under it turned near-black. A surface that opts out of the theme has to opt
   out of its tokens too. MM-205. */
.contact-settings-panel h5 {
    color: #333;
    font-size: 1rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-bottom: 24px;
    padding-bottom: 12px;
    border-bottom: 2px solid #f8f9fa;
}

.contact-settings-panel .form-label {
    font-weight: 600;
    color: #333;
    font-size: 0.85rem;
}

/* The contact edit form reuses the visitor's `.contact-info-item` markup, and
   its inputs need the text column to take the row. site.css has no rule for
   `.content` itself, so this is chrome rather than a copy (MM-300). */
.edit-mode .contact-info-item .content {
    flex: 1;
    min-width: 0;
}

/* Inline-editable inputs on the primary-color sidebar */
.contact-info-input {
    width: 100%;
    background: rgba(255, 255, 255, 0.2);
    border: 1px solid rgba(255, 255, 255, 0.5);
    border-radius: 6px;
    padding: 6px 10px;
    color: white;
    font-size: 0.95rem;
    font-family: inherit;
    margin-bottom: 4px;
}

.contact-info-input::placeholder {
    color: rgba(255, 255, 255, 0.6);
}

.contact-info-input:focus {
    outline: none;
    border-color: white;
    background: rgba(255, 255, 255, 0.3);
}

/* `.contact-form` is site.css's, and is deliberately not re-declared here.
   This file used to paint it a flat `white` — unscoped, so it hit the public
   page's markup wherever the editor stylesheet was loaded — which is the other
   half of what MM-205 reported. site.css paints it `var(--surface-color)`, the
   authored one-step-up-from-the-ground surface, which is what makes the card
   read as part of the page on a dark theme instead of a hole punched in it. */

/* ============================================================
   EDITOR NAVBAR PANELS (Social + Nav + Settings)
   CSS checkbox-hack panels: input:checked ~ .editor-panel → visible
   ============================================================ */

.editor-panel-toggle-input {
  display: none;
}

.editor-panel {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s ease-out;
  background: #f8fafc;
  border-bottom: 1px solid #e2e8f0;
}

.editor-panel-toggle-input:checked ~ .editor-panel {
  max-height: 400px;
  transition: max-height 0.3s ease-in;
}

.editor-panel-inner {
  border-top: 1px solid #e2e8f0;
}

.editor-panel-title {
  font-size: 0.875rem;
  font-weight: 600;
  color: #374151;
}

.social-input-icon {
  min-width: 2rem;
  justify-content: center;
  background: #fff;
  color: #64748b;
}

/* Stack the toggle inputs and panels in source order so :checked sibling selector works.
   The navbar div (publish-confirm wrapper) comes first in the DOM, then the inputs,
   then the panels. We position inputs AFTER the navbar div so the ~ combinator
   from input:checked can reach .editor-panel siblings. */

/* ============================================================
   THEME SWATCHES BAR
   ============================================================ */

.theme-swatches {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}

.theme-swatch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}

.theme-swatch-circle {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 3px solid transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.2s, transform 0.2s;
  position: relative;
  overflow: hidden;
}

.theme-swatch-circle .swatch-inner {
  position: absolute;
  inset: 0;
  border-radius: 50%;
}

.theme-swatch-circle .swatch-check {
  position: relative;
  z-index: 1;
  color: white;
  font-size: 1rem;
  display: none;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
}

.theme-swatch.active .theme-swatch-circle {
  border-color: var(--dark-color);
  transform: scale(1.1);
}

.theme-swatch.active .swatch-check {
  display: block;
}

.theme-swatch:hover .theme-swatch-circle {
  transform: scale(1.08);
}

.theme-swatch-name {
  font-size: 0.72rem;
  color: #666;
}

.theme-swatch.active .theme-swatch-name {
  color: var(--dark-color);
  font-weight: 700;
}

/* button_to generates <form> wrappers — make them transparent to the grid layout */
.theme-swatches form {
  display: contents;
}

.theme-swatches form button.theme-swatch {
  appearance: none;
  -webkit-appearance: none;
  font-family: inherit;
}

/* Extend prototype's .settings-field input rule to cover textarea */
.settings-field textarea {
  width: 100%;
  padding: 9px 12px;
  border: 1.5px solid #e0e0e0;
  border-radius: 8px;
  font-family: 'Roboto Slab', serif;
  font-size: 0.9rem;
  color: var(--dark-color, #2c3e50);
  resize: vertical;
  box-sizing: border-box;
}

.settings-field textarea:focus {
  outline: none;
  border-color: var(--primary-color, #7d9d9c);
  box-shadow: 0 0 0 3px rgba(125, 157, 156, 0.12);
}

/* ─── Nav Edit Panel ─── */
.nav-edit-wrapper {
  position: relative;
  display: flex;
  align-items: center;
}

.nav-edit-btn {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1.5px solid var(--primary-color);
  background: white;
  color: var(--primary-color);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 0.7rem;
  transition: all 0.2s;
  flex-shrink: 0;
  margin-left: 8px;
}

.nav-edit-btn:hover {
  background: var(--primary-color);
  color: white;
  transform: scale(1.1);
}

.nav-edit-btn.active {
  background: var(--accent-color);
  border-color: var(--accent-color);
  color: white;
}

.nav-edit-panel {
  position: absolute;
  top: calc(100% + 14px);
  right: 0;
  width: 400px;
  max-height: 70vh;
  overflow-y: auto;
  background: white;
  border-radius: 12px;
  box-shadow: 0 10px 40px rgba(0,0,0,0.18);
  padding: 24px;
  z-index: 10001;
  display: none;
  animation: modalSlideIn 0.25s ease;
}

.nav-edit-panel.visible {
  display: block;
}

.nav-edit-panel h4 {
  font-family: "Roboto Slab", serif;
  font-weight: 700;
  font-size: 1.1rem;
  color: var(--primary-color);
  margin-bottom: 18px;
}

.nav-edit-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid rgba(0,0,0,0.06);
}

.nav-edit-row:last-child {
  border-bottom: none;
}

.nav-edit-label {
  width: 110px;
  flex-shrink: 0;
  padding: 7px 10px;
  border: 1.5px solid #e0e0e0;
  border-radius: 6px;
  font-family: "Roboto Slab", serif;
  font-size: 0.82rem;
  color: var(--dark-color);
  transition: border-color 0.2s, box-shadow 0.2s;
}

.nav-edit-url {
  flex: 1;
  min-width: 0;
  padding: 7px 10px;
  border: 1.5px solid #e0e0e0;
  border-radius: 6px;
  font-family: "Roboto Slab", serif;
  font-size: 0.82rem;
  color: var(--dark-color);
  transition: border-color 0.2s, box-shadow 0.2s;
}

.nav-edit-label:focus,
.nav-edit-url:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px rgba(125, 157, 156, 0.12);
}

@media (max-width: 991px) {
  .nav-edit-panel {
    position: fixed;
    top: 50%;
    left: 50%;
    right: auto;
    transform: translate(-50%, -50%);
    width: 90vw;
    max-width: 400px;
  }
}

/* ─── Settings Panel ─── */
.settings-btn {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1.5px solid var(--primary-color);
  background: white;
  color: var(--primary-color);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 0.75rem;
  transition: all 0.2s;
  flex-shrink: 0;
  margin-left: 6px;
}

.settings-btn:hover {
  background: var(--primary-color);
  color: white;
  transform: scale(1.1);
}

.settings-group {
  margin-bottom: 24px;
}

.settings-group-label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--primary-color);
  margin-bottom: 12px;
  padding-bottom: 6px;
  border-bottom: 1.5px solid var(--light-color);
}

.settings-field {
  margin-bottom: 14px;
}

.settings-field label {
  display: block;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--dark-color);
  margin-bottom: 5px;
}

.settings-field input {
  width: 100%;
  padding: 9px 12px;
  border: 1.5px solid #e0e0e0;
  border-radius: 8px;
  font-family: 'Roboto Slab', serif;
  font-size: 0.9rem;
  color: var(--dark-color);
  transition: border-color 0.2s, box-shadow 0.2s;
}

.settings-field input:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px rgba(125,157,156,0.12);
}

.settings-domain-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.settings-domain-row input {
  flex: 1;
}

.settings-domain-sep {
  color: #aaa;
  font-size: 0.9rem;
  flex-shrink: 0;
}

/* ── Editor sidebars ── */

/* The `.editor-sidebar*` rules that lived here were removed with MM-121–124.
   They were dead: every editor sidebar is a shadcn <Sheet> and nothing in
   app/javascript or app/views referenced these classes. Kept out rather than
   commented out — a second, unused sidebar implementation is exactly the kind
   of thing a future change copies from by mistake. */
.social-edit-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 0;
    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}

.social-edit-row:last-of-type {
    border-bottom: none;
}

.social-edit-icon {
    width: 36px;
    height: 36px;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-size: 1rem;
    flex-shrink: 0;
}

.social-edit-row.disabled .social-edit-icon {
    opacity: 0.35;
}

.social-edit-icon.facebook  { background: #1877f2; }
.social-edit-icon.instagram { background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888); }
.social-edit-icon.youtube   { background: #ff0000; }
.social-edit-icon.linkedin  { background: #0a66c2; }
.social-edit-icon.tiktok    { background: #010101; }
.social-edit-icon.twitter   { background: #1da1f2; }
.social-edit-icon.bluesky   { background: #0085ff; }
.social-edit-icon.substack  { background: #ff6719; }

.social-edit-url {
    flex: 1;
    min-width: 0;
    padding: 7px 10px;
    border: 1.5px solid #e0e0e0;
    border-radius: 6px;
    font-size: 0.85rem;
    color: var(--dark-color);
    transition: border-color 0.2s, box-shadow 0.2s;
}

.social-edit-url:focus {
    outline: none;
    border-color: var(--primary-color);
    box-shadow: 0 0 0 3px rgba(125, 157, 156, 0.12);
}

.social-edit-row.disabled .social-edit-url {
    opacity: 0.4;
    pointer-events: none;
}

/* Toggle switch */
.social-edit-toggle {
    position: relative;
    width: 38px;
    height: 22px;
    flex-shrink: 0;
}

.social-edit-toggle input {
    display: none;
}

.social-edit-toggle .toggle-slider {
    position: absolute;
    inset: 0;
    background: #ccc;
    border-radius: 11px;
    cursor: pointer;
    transition: background 0.2s;
}

.social-edit-toggle .toggle-slider::after {
    content: "";
    position: absolute;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: white;
    top: 3px;
    left: 3px;
    transition: transform 0.2s;
}

.social-edit-toggle input:checked + .toggle-slider {
    background: var(--primary-color);
}

.social-edit-toggle input:checked + .toggle-slider::after {
    transform: translateX(16px);
}

.social-panel-actions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
}

/* ── Workshops & Retreats Editor ── */
.workshop-card-wrapper {
    position: relative;
}

.workshop-card-wrapper.unpublished .workshop-card {
    opacity: 0.5;
}

.workshop-card-wrapper .card-edit-controls {
    display: none;
}

.editable-section.editing .workshop-card-wrapper .card-edit-controls {
    display: flex;
}

.editable-section.editing .workshop-card-wrapper .event-type {
    opacity: 0;
    pointer-events: none;
}

.workshop-draft-badge {
    position: absolute;
    top: 15px;
    left: 15px;
    background: rgba(0, 0, 0, 0.6);
    color: white;
    padding: 4px 12px;
    border-radius: 15px;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    z-index: 3;
    display: none;
}

.editable-section.editing .workshop-card-wrapper.unpublished .workshop-draft-badge {
    display: block;
}

.add-workshop-card {
    border: 3px dashed var(--primary-color, #7c3aed);
    border-radius: 10px;
    background: rgba(125, 157, 156, 0.05);
    display: none;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: 12px;
    min-height: 380px;
    cursor: pointer;
    transition: all 0.3s ease;
    color: var(--primary-color, #7c3aed);
}

.editable-section.editing .add-workshop-card {
    display: flex;
}

.add-workshop-card:hover {
    background: rgba(125, 157, 156, 0.12);
    border-color: var(--accent-color, #5b21b6);
    transform: translateY(-4px);
    box-shadow: 0 8px 24px rgba(125, 157, 156, 0.2);
}

.add-workshop-card i {
    font-size: 2.5rem;
    opacity: 0.8;
}

.add-workshop-card span {
    font-weight: 600;
    font-size: 1.05rem;
}

/* Unsplash picker (MM-163) — editor chrome inside Uppy's Dashboard, so it
   belongs here rather than in site.css, which is shared with the public page.

   The photographs are the content; everything else is scaffolding and stays
   quiet. Neutrals only, one accent borrowed from Uppy's own primary, and the
   single deliberate move is the credit that appears on hover rather than
   sitting permanently across every thumbnail. */
.uppy-unsplash {
  display: flex;
  flex-direction: column;
  /* `flex: 1` + `min-height: 0`, not `height: 100%`. Uppy's picker panel is a
     flex column (its header bar sits above this), and a percentage height on a
     flex item resolves against an indefinite height — so the panel grew to fit
     every result and the Dashboard scrolled instead of the grid. `min-height: 0`
     is the half people miss: without it a flex item refuses to shrink below its
     content, and the overflow container below can never become scrollable. */
  flex: 1 1 auto;
  min-height: 0;
  background: #fff;
}

.uppy-unsplash-search {
  /* A fixed header row, not `position: sticky`. Sticky was doing nothing here:
     it only pins within the element's own scroll container, and the scrolling
     element is the results list *beside* this one, not an ancestor of it.
     `flex: 0 0 auto` is what actually holds it at the top — the row keeps its
     height while the results below absorb all the free space and scroll. */
  flex: 0 0 auto;
  display: flex;
  gap: 0.5rem;
  padding: 0.875rem 1rem;
  background: #fff;
  border-bottom: 1px solid #eceef1;
}

.uppy-unsplash .uppy-unsplash-field {
  appearance: none;
  flex: 1;
  min-width: 0;
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
  color: #1f2933;
  background: #fff;
  /* A visible edge, not a transparent one over a grey fill. The field has to
     read as somewhere you can type before you have clicked it — the filled
     borderless treatment left it looking like a disabled control. */
  border: 1px solid #cbd2d9;
  border-radius: 6px;
}

.uppy-unsplash .uppy-unsplash-field:hover { border-color: #9aa5b1; }

.uppy-unsplash .uppy-unsplash-field::placeholder { color: #9aa5b1; }

.uppy-unsplash .uppy-unsplash-field:focus-visible,
.uppy-unsplash .uppy-unsplash-field:focus {
  outline: none;
  border-color: #1269cf;
  box-shadow: 0 0 0 3px rgb(18 105 207 / 18%);
}

.uppy-unsplash .uppy-unsplash-submit {
  /* Explicit, because Uppy's reset zeroes appearance and border on buttons and
     loads after this file — an unstyled submit renders as bare text. */
  appearance: none;
  border: 1px solid #1269cf;
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: #fff;
  background: #1269cf;
  border-radius: 6px;
  cursor: pointer;
}

.uppy-unsplash .uppy-unsplash-submit:hover:not(:disabled) {
  background: #0f56aa;
  border-color: #0f56aa;
}

.uppy-unsplash .uppy-unsplash-submit:disabled {
  background: #cbd2d9;
  border-color: #cbd2d9;
  cursor: default;
}

.uppy-unsplash .uppy-unsplash-submit:focus-visible {
  outline: 2px solid #1269cf;
  outline-offset: 2px;
}

.uppy-unsplash-results {
  flex: 1 1 auto;
  min-height: 0;
  /* The scroll lives here, so the sticky search bar above stays put.
     `max-height` rather than relying solely on the flex chain: Uppy wraps plugin
     output in markup we do not control, and if any wrapper in that chain lacks a
     definite height the flex sizing silently degrades and the Dashboard scrolls
     instead. This bound holds regardless of what Uppy does above us. */
  max-height: 60vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1rem;
}

/* Two columns, fixed. The panel is ~650px wide, so two tiles give each photo
   enough room to be judged on its merits — which is the whole task here. */
.uppy-unsplash-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.75rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.uppy-unsplash-tile {
  display: block;
  width: 100%;
  cursor: pointer;
  text-align: left;
}

/* A fixed ratio keeps the rhythm even while thumbnails stream in at whatever
   dimensions Unsplash returns. Applied to the image rather than the tile, so
   the caption below it is not squeezed into the ratio. */
.uppy-unsplash-tile img,
.uppy-unsplash-skeleton {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  border-radius: 6px;
  background: #f0f2f5;
  transition: transform 200ms ease;
}

.uppy-unsplash-tile:hover img,
.uppy-unsplash-tile:focus-visible img { transform: scale(1.04); }

.uppy-unsplash-tile:focus-visible {
  outline: 2px solid #1269cf;
  outline-offset: 2px;
}

.uppy-unsplash-credit {
  display: block;
  padding-top: 0.375rem;
  font-size: 0.75rem;
  line-height: 1.3;
  color: #52606d;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.uppy-unsplash-skeleton {
  animation: uppy-unsplash-pulse 1.4s ease-in-out infinite;
}

@keyframes uppy-unsplash-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

.uppy-unsplash-message {
  margin: 2.5rem auto 0;
  max-width: 22rem;
  font-size: 0.875rem;
  line-height: 1.5;
  color: #52606d;
  text-align: center;
}

.uppy-unsplash-message--start { color: #7b8794; }

.uppy-unsplash-more {
  margin: 0.75rem 0 0;
  font-size: 0.75rem;
  color: #7b8794;
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .uppy-unsplash-tile img { transition: none; }

  .uppy-unsplash-tile:hover img,
  .uppy-unsplash-tile:focus-visible img { transform: none; }

  .uppy-unsplash-skeleton { animation: none; }
}

/* ------------------------------------------------------------------
   Quick links editor (MM-183).
   ------------------------------------------------------------------
   Almost nothing lives here any more, and that is the point.

   This screen used to carry a second, hand-rolled design language for the
   application subdomain: its own sage/near-black palette, its own radii and
   type scale, and card accents keyed to `var(--primary-color)` — so the
   *editor* changed colour with the site being edited. Next to /analytics it
   read as a different product. The chrome is now Tailwind in the components,
   sharing one vocabulary through `lib/pageChrome.ts` and one
   palette through `lib/buttonTone.ts`.

   What remains is the two things utilities cannot express: the preview frame,
   and the layout diagrams, which are absolutely-positioned shapes rather than
   content.

   Editor chrome only. Everything the preview renders comes from site.css,
   because the preview IS the public page component — duplicating its rules
   here is what let the footers drift apart originally. */

/* The preview frame. Stickiness is handled by the page's own layout classes;
   this is only the bezel. `.ql-phone` is also the boundary the end-to-end
   suite uses to tell the preview apart from the chrome around it. */
.ql-phone {
  border: 1px solid #e4e7e6;
  border-radius: 1.75rem;
  overflow: hidden;
  box-shadow: 0 12px 40px rgb(44 54 57 / 12%);
}

.ql-phone .quick-links { min-height: 34rem; }

/* The layout chooser's thumbnails — miniature diagrams of the three
   arrangements. Deliberately grey rather than themed: they illustrate where
   the pieces sit, and tinting them would imply the choice changes the
   palette, which is the one thing it does not do. */
.ql-layout-thumb {
  display: block;
  position: relative;
  height: 74px;
  border-radius: 8px;
  overflow: hidden;
  background: #f4f6f5;
}

.ql-thumb-banner { position: absolute; inset: 0 0 auto; height: 26px; background: #d7dcda; display: block; }
.ql-thumb-avatar { position: absolute; width: 18px; height: 18px; border-radius: 50%; background: #9aa5a1; border: 2px solid #f4f6f5; display: block; }
.ql-thumb-line { position: absolute; height: 4px; border-radius: 2px; background: #b9c2bf; display: block; }
.ql-thumb-rows { position: absolute; left: 10px; right: 10px; display: flex; flex-direction: column; gap: 4px; }
.ql-thumb-rows span { height: 7px; border-radius: 4px; background: #c8d0ce; display: block; }

.ql-layout-thumb-stack .ql-thumb-avatar { left: 50%; transform: translateX(-50%); top: 17px; }
.ql-layout-thumb-stack .ql-thumb-line { left: 50%; transform: translateX(-50%); width: 32px; top: 39px; }
.ql-layout-thumb-stack .ql-thumb-rows { top: 48px; }

.ql-layout-thumb-card .ql-thumb-banner { height: 40px; }
.ql-layout-thumb-card .ql-thumb-avatar { left: 50%; transform: translateX(-50%); top: 22px; }
.ql-layout-thumb-card .ql-thumb-line { left: 50%; transform: translateX(-50%); width: 28px; top: 44px; }
.ql-layout-thumb-card .ql-thumb-rows { top: 53px; left: 16px; right: 16px; }

.ql-layout-thumb-masthead .ql-thumb-banner { height: 18px; }
.ql-layout-thumb-masthead .ql-thumb-avatar { left: 10px; top: 10px; }
.ql-layout-thumb-masthead .ql-thumb-line { left: 34px; width: 30px; top: 16px; }
.ql-layout-thumb-masthead .ql-thumb-rows { top: 36px; }

/* ─── Two-Tier Editor Header (MM-196) ───────────────────────────────────────
 *
 * Row one is the account bar: where the site lives, whether it is live, and
 * the tools that are not part of the page. Row two is the visitor's own
 * `PublicNavbar`, and nothing else.
 *
 * THE PALETTE SPLIT IS LOAD-BEARING. Row one uses the Tailwind indigo/gray app
 * palette taken from the analytics screens, so it reads as *myMat's* UI. Row
 * two is on the teacher's theme tokens, because it is her site's navbar. That contrast is the whole point of the
 * split — do NOT "consistently" convert row one to theme tokens.
 *
 * Row one's exact values (its type sizes, 780px/600px breakpoints) are why this is CSS
 * rather than utility classes: Tailwind's scale and its 768/640 breakpoints
 * cannot express them without a wall of arbitrary values.
 */

.editor-header {
    position: sticky;
    top: 0;
    z-index: 40;
    /* What row two's translucency composites against, so it must be the same
       ground the visitor's navbar has behind it (MM-214). `--navbar-bg` is
       `rgba(light_color, .95)`: declaring that value on both surfaces while
       resolving it over white here and over the page ground there is a fix that
       passes a computed-style comparison and is still visibly 5% lighter than
       the site it previews. On a light theme `--page-bg` is `#ffffff`, i.e.
       exactly the literal it replaces. */
    background: var(--page-bg, #fff);
}

/* ── Row one: account bar ── */
.editor-account-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    height: 48px;
    padding: 0 20px;
    background: #111827;
    color: #f9fafb;
    font-size: 13px;
}

.editor-account-bar__wordmark {
    font-family: 'Roboto Slab', serif;
    font-weight: 700;
    font-size: 14px;
    letter-spacing: .01em;
}

.editor-account-bar__sep { color: #4b5563; }

.editor-account-bar__url {
    position: relative;
    display: flex;
    align-items: center;
    gap: 7px;
    color: #e5e7eb;
    min-width: 0;
}

.editor-account-bar__host {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.editor-account-bar__copy {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 4px;
    border-radius: 6px;
    color: #9ca3af;
    transition: background-color 120ms ease, color 120ms ease;
}

.editor-account-bar__copy:hover { background: rgba(249, 250, 251, .08); color: #e5e7eb; }

/* A toast, as the design calls for — and out of flow so it cannot reflow the
   bar. An inline slot moved the state pill 47px and back; a reserved-width
   slot still drifted ~5px (the reservation must match the rendered text
   exactly, and does not); positioning it only while non-empty still moved the
   pill 7px, because the empty span was still taking a flex gap.

   So it is absolute in BOTH states: zero layout contribution by construction.
   Absolute rather than display:none while idle because it is an aria-live
   region — one that is hidden or unmounted between announcements is not
   reliably announced when it returns. */
.editor-account-bar__copied {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 45;
    color: #c7d2fe;
    font-size: 12px;
    white-space: nowrap;
}

/* It overlays the site navbar below, whose ground is the theme's, so it carries its own. */
.editor-account-bar__copied:not(:empty) {
    padding: 4px 9px;
    border-radius: 6px;
    background: #111827;
    box-shadow: 0 6px 18px rgba(17, 24, 39, .22);
}

.editor-state-pill {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    border-radius: 999px;
    background: rgba(249, 250, 251, .1);
    color: #d1d5db;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
    /* Draft → Live is a cross-fade, not a swap (design: 150ms ease). */
    transition: background-color 150ms ease, color 150ms ease;
}

.editor-state-pill--live {
    background: rgba(129, 140, 248, .18);
    color: #c7d2fe;
}

.editor-state-pill__dot {
    width: 6px;
    height: 6px;
    border-radius: 999px;
    background: #818cf8;
}

.editor-account-bar__spacer { flex: 1; }

/* No layout shift between draft and live: the CTA slot keeps its width so the
   controls to its right do not jump when the state changes. */
.editor-account-bar__cta {
    display: flex;
    justify-content: flex-end;
    min-width: 132px;
}

.editor-bar-btn {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 7px 16px;
    border-radius: 8px;
    background: #4f46e5;
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    white-space: nowrap;
    transition: background-color 120ms ease;
}

.editor-bar-btn:hover { background: #6366f1; }

.editor-bar-btn--outline {
    padding: 6px 12px;
    background: transparent;
    border: 1px solid #374151;
    color: #e5e7eb;
    font-weight: 500;
}

.editor-bar-btn--outline:hover { background: rgba(249, 250, 251, .08); }

.editor-bar-divider {
    width: 1px;
    height: 22px;
    background: #374151;
    margin: 0 4px;
}

.editor-bar-tool {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    border-radius: 8px;
    color: #d1d5db;
    font-size: 13px;
    white-space: nowrap;
    transition: background-color 120ms ease;
}

.editor-bar-tool:hover { background: rgba(249, 250, 251, .08); }

.editor-avatar-trigger {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 4px 8px 4px 4px;
    border-radius: 999px;
    color: #d1d5db;
    transition: background-color 120ms ease;
}

.editor-avatar-trigger:hover,
.editor-avatar-trigger[aria-expanded='true'] { background: rgba(249, 250, 251, .08); }

/* The account trigger's avatar (MM-219). Shape, sizing and the monogram itself
 * come from the shared `.avatar` component in site.css; this supplies only what
 * makes it *chrome* rather than preview.
 *
 * `width`/`height`/`font-size` are gone deliberately: the component sets them
 * inline from its `size` prop, because the monogram has to scale with the
 * frame. A copy here would be the second source of one number, and being
 * unlayered it would win — the silent duplicate this file is warned about.
 *
 * This row is row *one*, the dark app bar. It brings its own ground, so it
 * keeps its fixed indigo ink (the case of chrome that brings its own ground in
 * MM-214, not the `.nav-rollup__trigger` one). */
.editor-avatar {
    background: linear-gradient(135deg, #6366f1, #818cf8);
    color: #fff;
    font-weight: 700;
}

/* Overriding the pair, not half of it (MM-212). The shared monogram paints
 * `--primary-color` on `--light-color`, which is right on the teacher's page
 * and wrong on this bar — it would put her theme colour over the indigo above.
 * Ground and ink are both handed back to `.editor-avatar`, and the typeface
 * with them: row one is chrome and does not use her heading font. */
.editor-avatar .avatar__monogram {
    background: none;
    color: inherit;
    font-family: inherit;
}

/* ── Row two: the visitor's own navbar ──
 *
 * `EditorNavbar` renders `PublicNavbar` here, so row two is styled by
 * `site.css` exactly as it is for a visitor. This file used to restate that
 * row one declaration at a time, under `.editor-site-*`. The copy drifted five
 * times (MM-196, MM-214, MM-215, MM-266, then the gutter). Its last state was
 * 72px against 58px, a 22px gap against 20px, no divider, no brand
 * letter-spacing and no Book Now, all under parity specs that each compared
 * only the properties someone had listed.
 *
 * The one rule does two things, and nothing else belongs in it:
 *
 * - `position: static`. `.navbar` is `fixed` for a visitor, where it floats over
 *   her page. Here it sits in the sticky header under row one.
 * - The four properties the navbar INHERITS from a visitor's page root. They are
 *   not navbar rules: site.css sets them on `.landing-page body,
 *   .landing-page-preview`, and this header is outside both. Without them the
 *   brand was 2px shorter (line-height 1.5 against 1.6) and the row 56px
 *   against 58px. The root context cannot be borrowed by wrapping the navbar in
 *   `.landing-page-preview`: that rule also sets `overflow-x: hidden`, which
 *   forces `overflow-y: auto` and would clip the roll-up panel hanging below the
 *   row, and code and specs take the FIRST `.landing-page-preview` to be the
 *   canvas.
 *
 * Everything else about the row stays site.css's, and must: this file is
 * unlayered, so a restated navbar rule here would outrank the original
 * silently. `e2e/parity/editor-menu-theme.spec.ts`, "the editor menu row lays
 * out exactly as the navbar it previews", measures the outcome, so drift on
 * either side goes red. */
.editor-header .navbar {
    position: static;
    font-family: var(--body-font);
    font-weight: 400;
    line-height: 1.6;
    color: var(--dark-color);
}

/* ── Avatar menu ── */
.editor-account-menu {
    position: absolute;
    top: 56px;
    right: 12px;
    z-index: 50;
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 276px;
    padding: 8px;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 16px;
    box-shadow: 0 20px 44px rgba(17, 24, 39, .14);
    font-size: 14px;
    color: #374151;
    animation: editor-account-menu-in 120ms ease-out;
}

@keyframes editor-account-menu-in {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .editor-account-menu { animation: none; }
}

/* ── The account panel's photo control (MM-220) ──
 *
 * The trigger is laid over the avatar rather than sitting beneath it as a
 * button. `ImageUploader`'s modal trigger renders `<Camera/>` plus a `<span>`
 * and nothing else, so those two are given the two jobs this needs: the camera
 * is the resting badge, the span is the hover copy. The component itself is not
 * touched -- it carries guards only `e2e/editor/avatar.spec.ts` can see.
 *
 * `--account-avatar-size` comes from `PREVIEW_PX` in the component. CSS has no
 * other way to read it, and a literal here would be a second copy of a number
 * that must agree exactly or the scrim stops covering the photo.
 *
 * The container is deliberately NOT given a fixed height: `UploadStatus` renders
 * an error paragraph as a sibling of the trigger, and it has to flow below the
 * avatar rather than be clipped inside it. The trigger is centred on `left: 50%`
 * for the same reason -- a wide error message widens the container, and
 * `left: 0` would then leave the scrim off the photo.
 */
.account-avatar-uploader {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
}


/* Two surfaces lay an `ImageUploader` modal trigger OVER the image it changes
 * rather than putting a button beneath it: the account photo (MM-220) and the
 * site logo (MM-265). Everything about how that BEHAVES is shared by selector
 * below; only the geometry -- what shape the box is and where it sits -- is
 * per-surface.
 *
 * Shared by selector rather than copied. A second rule under a different name
 * never loses and never reports, which is the hazard this stylesheet already
 * documents one layer up for `site.css` duplicates; here the two copies would
 * simply drift with nothing able to notice.
 */
.account-avatar-uploader__trigger,
.settings-logo-uploader__trigger {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    /* Transparent at rest: the image is the thing being judged, and a permanent
       scrim would darken the very thing the teacher is here to approve. */
    background: rgba(17, 24, 39, 0);
    transition: background-color 140ms ease;
}

.account-avatar-uploader__trigger {
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    width: var(--account-avatar-size);
    height: var(--account-avatar-size);
    border-radius: 9999px;
}

/* Geometry, not typography: the copy has to fit a circle. The logo's frame is
   the width of the sheet and needs no bound. */
.account-avatar-uploader__trigger > span {
    max-width: calc(var(--account-avatar-size) - 32px);
}

/* The resting affordance. A hover-only control is invisible to touch, where
   there is no hover at all -- so the badge is always present and the scrim is
   the enhancement, not the only signal. */
.account-avatar-uploader__trigger > svg,
.settings-logo-uploader__trigger > svg {
    position: absolute;
    right: 2px;
    bottom: 2px;
    width: 28px;
    height: 28px;
    padding: 6px;
    border-radius: 9999px;
    background: #fff;
    color: #374151;
    box-shadow: 0 1px 3px rgba(17, 24, 39, .28);
    transition: transform 140ms ease;
}

/* "Change photo" / "Choose photo" -- `ImageUploader` decides which, and the
   same string is the button's `aria-label`, so the visible copy and the
   accessible name cannot drift. */
.account-avatar-uploader__trigger > span,
.settings-logo-uploader__trigger > span {
    color: #fff;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.25;
    text-align: center;
    opacity: 0;
    transition: opacity 140ms ease;
}

.account-avatar-uploader__trigger:hover,
.account-avatar-uploader__trigger:focus-visible,
.settings-logo-uploader__trigger:hover,
.settings-logo-uploader__trigger:focus-visible {
    background: rgba(17, 24, 39, .58);
}

.account-avatar-uploader__trigger:hover > span,
.account-avatar-uploader__trigger:focus-visible > span,
.settings-logo-uploader__trigger:hover > span,
.settings-logo-uploader__trigger:focus-visible > span { opacity: 1; }

.account-avatar-uploader__trigger:hover > svg,
.account-avatar-uploader__trigger:focus-visible > svg,
.settings-logo-uploader__trigger:hover > svg,
.settings-logo-uploader__trigger:focus-visible > svg { transform: scale(1.06); }

/* The scrim reads as hover, not as focus, so keyboard users get a ring as well
   -- otherwise the two states are indistinguishable and only one of them can be
   reached without a mouse. */
.account-avatar-uploader__trigger:focus-visible,
.settings-logo-uploader__trigger:focus-visible {
    outline: 2px solid #4f46e5;
    outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
    .account-avatar-uploader__trigger,
    .account-avatar-uploader__trigger > svg,
    .account-avatar-uploader__trigger > span,
    .settings-logo-uploader__trigger,
    .settings-logo-uploader__trigger > svg,
    .settings-logo-uploader__trigger > span { transition: none; }
}

/* MM-265. The site logo, drawn in the panel that edits it.
 *
 * Rendered only when there IS a logo -- an empty frame would promise a box the
 * site does not reserve either (`site.css` scopes the header's `min-height`
 * with `:has()` for exactly that reason).
 *
 * The frame is a child rather than this element, because modal `ImageUploader`
 * returns its `UploadStatus` paragraphs as SIBLINGS of the trigger: an upload
 * error rendered inside the framed box would sit under the scrim, unreadable
 * and unreachable. `.account-avatar-uploader` records the same hazard from the
 * other side, which is why its container has no fixed height either.
 *
 * Fixed ground, fixed ink, deliberately: this is a chrome sheet that brings its
 * own background, so by MM-214's dividing line it keeps its own neutral
 * palette rather than following the teacher's theme.
 */
.settings-logo-uploader {
    /* One declaration of the height, read by the frame and by the trigger that
       has to cover it exactly. Local to this block because the number lives
       nowhere else -- unlike `--account-avatar-size`, which CSS can only learn
       from the component. */
    --logo-preview-h: 72px;
    position: relative;
    display: flex;
    flex-direction: column;
}

.settings-logo-uploader__frame {
    display: flex;
    align-items: center;
    justify-content: center;
    height: var(--logo-preview-h);
    padding: 12px;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    background: #f9fafb;
    overflow: hidden;
}

/* Drawn at the size a VISITOR gets, from `site.css`'s own tokens -- a custom
   property is the only channel these two stylesheets share (MM-215), and the
   fallbacks stay literal per the MM-214 convention.
 *
 * No `object-fit` here, and its absence is the point rather than an omission:
 * `SiteLogoProjection` scales a logo whole with `resize_to_limit` because a
 * logo is not a photograph and must never be cropped, and with both bounds set
 * and no `width`/`height` the browser already does exactly that. `object-fit:
 * contain` alongside them is a no-op -- measured, by deleting it against
 * `e2e/editor/logo.spec.ts`'s aspect-ratio assertion, which holds either way
 * and fails the moment the box IS given a size.
 */
.settings-logo-uploader__image {
    max-width: var(--logo-max-w, 220px);
    max-height: var(--logo-max-h, 40px);
}

.settings-logo-uploader__trigger {
    top: 0;
    left: 0;
    right: 0;
    height: var(--logo-preview-h);
    border-radius: 8px;
}

/* Clear of the frame's own border. The shared 2px is measured against a circle,
   which curves away from its badge; a rectangle does not, so the badge lands on
   the corner. */
.settings-logo-uploader__trigger > svg {
    right: 6px;
    bottom: 6px;
}

/* Deeper than the shared scrim, and the number is measured rather than picked.
   The label is white, and the ground it has to beat here is known: `#f9fafb`,
   the frame's own. At the shared `.58` that composites to a contrast ratio of
   4.4996:1 -- AA to two decimal places and a failure at the third. `.66` gives
   5.86:1 and still lets the mark read through.
 *
 * Per-surface rather than a change to the shared value, because the avatar's
 * scrim sits over a PHOTOGRAPH, whose ground is unknown: the same reasoning
 * cannot be applied to it from here, and the number that is right for a known
 * light ground is not thereby right for an arbitrary one. (That surface may
 * well have a worse version of this problem. It is MM-220's and is not touched
 * here.)
 */
.settings-logo-uploader__trigger:hover,
.settings-logo-uploader__trigger:focus-visible {
    background: rgba(17, 24, 39, .66);
}

/* MM-220. The identity block is the control that opens the account panel, not
 * a caption above the rows -- so it is a `menuitem` and takes the same hover,
 * focus and radius as `__row`. It keeps fixed ink deliberately: `.editor-account-menu`
 * paints its own `#fff` ground, and per the MM-214 rule chrome that brings its
 * own ground keeps its own ink.
 *
 * A row rather than a column now, because the avatar moved into it. `min-width: 0`
 * on the text column is load-bearing: `__meta`'s `text-overflow: ellipsis` does
 * nothing inside a flex item that is free to grow past its container, and a long
 * address would push the chevron out of the menu instead of truncating. */
.editor-account-menu__identity {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 12px;
    border-radius: 10px;
    color: inherit;
    text-align: left;
    transition: background-color 120ms ease;
}

.editor-account-menu__identity:hover,
.editor-account-menu__identity:focus-visible { background: #f9fafb; }

.editor-account-menu__identity > svg { flex: none; color: #9ca3af; }

.editor-account-menu__identity-avatar { flex: none; }

.editor-account-menu__identity-text {
    display: flex;
    flex: 1;
    min-width: 0;
    flex-direction: column;
    gap: 2px;
}

.editor-account-menu__name { font-weight: 600; color: #111827; }

/* The address truncates; the plan does not. The design has always shown
 * `sarah@… · Pro`, and the plan being readable here without opening billing is
 * why the identity block carries it at all.
 *
 * `text-overflow: ellipsis` was declared on this rule from the start and did
 * nothing: it needs `white-space: nowrap`, and without it the line wrapped
 * instead — three lines for a long address, which the avatar arriving in this
 * block (MM-220) only made more likely. The truncation now lives on the address
 * alone, so the suffix cannot be the part that gets cut. */
.editor-account-menu__meta {
    display: flex;
    gap: 4px;
    font-size: 12.5px;
    color: #9ca3af;
    white-space: nowrap;
}

.editor-account-menu__email {
    overflow: hidden;
    text-overflow: ellipsis;
}

.editor-account-menu__divider {
    height: 1px;
    background: #f3f4f6;
    margin: 4px;
}

.editor-account-menu__group {
    padding: 8px 12px 4px;
    font-size: 11px;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: #9ca3af;
}

.editor-account-menu__row {
    display: flex;
    align-items: center;
    gap: 11px;
    width: 100%;
    padding: 9px 12px;
    border-radius: 10px;
    color: inherit;
    text-align: left;
    transition: background-color 120ms ease;
}

.editor-account-menu__row:hover,
.editor-account-menu__row:focus-visible { background: #f9fafb; }

.editor-account-menu__row--muted { color: #6b7280; }
.editor-account-menu__row--danger { color: #b91c1c; }
.editor-account-menu__row--danger:hover,
.editor-account-menu__row--danger:focus-visible { background: #fef2f2; }

/* ── Narrow: 780px and below ──
 * The account bar keeps only state, publish and avatar; the URL, Visitors and
 * Settings move into the avatar menu, and the site nav collapses. */
@media (max-width: 780px) {
    .editor-account-bar {
        height: 46px;
        gap: 10px;
        padding: 0 14px;
        font-size: 12.5px;
    }

    .editor-account-bar__wordmark,
    .editor-account-bar__sep,
    .editor-account-bar__url,
    .editor-bar-divider,
    .editor-bar-tool { display: none; }

    .editor-account-bar__cta { min-width: 0; }

    .editor-bar-btn { padding: 6px 13px; font-size: 12.5px; }

    .editor-avatar { width: 24px; height: 24px; font-size: 10.5px; }

    .editor-account-menu { right: 8px; }
}

/* Row two's editing tool steps down where the visitor's navbar gives up its
   menu (`PublicNavbar` collapses at Tailwind's `lg`). Row one cannot fit it
   beside Visitors and Settings below about 950px, and the avatar menu carries
   it at every width. Row one otherwise keeps its own 780px breakpoint. */
@media (max-width: 1024px) {
    .editor-bar-tool--menu { display: none; }
}

/* Publish confirmation, anchored under the account bar's primary action. */
.editor-publish-confirm {
    position: absolute;
    right: 0;
    top: 100%;
    z-index: 50;
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    padding: 12px;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 12px;
    box-shadow: 0 20px 44px rgba(17, 24, 39, .14);
}

.editor-publish-confirm__yes,
.editor-publish-confirm__cancel {
    padding: 6px 12px;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 600;
    white-space: nowrap;
    transition: background-color 120ms ease;
}

.editor-publish-confirm__yes { background: #4f46e5; color: #fff; }
.editor-publish-confirm__yes:hover { background: #6366f1; }

.editor-publish-confirm__cancel {
    border: 1px solid #d1d5db;
    color: #374151;
    font-weight: 500;
}

.editor-publish-confirm__cancel:hover { background: #f9fafb; }

/* ─── Character Counter (MM-198) ─── */
/* The `<count>/<max>` readout under editor text areas. Editor chrome — it never
   renders on a public page, so it does not belong in site.css.
   Unlayered here, while site.css's section rules sit in `@layer components`, so
   these declarations win inside a section regardless of specificity — the same
   mechanism `.cta-url-combobox-hint` relies on to cancel `.hero p`'s margin. */
.character-counter {
  margin: 0.25rem 0 0;
  text-align: right;
  font-size: 0.75rem;
  line-height: 1rem;
  /* The token, not a copy of its value: `#6b7280` was correct for Tailwind v3
     and would silently become a stale snapshot the day the theme moves.
     Tailwind emits a token only where some utility uses it, so the fallback
     covers `text-muted-foreground` disappearing from the scanned source. */
  color: var(--color-muted-foreground, #6b7280);
  /* Digits share a width, so the readout does not shuffle sideways as the
     count ticks past 9, 99, 199 while the teacher is typing. */
  font-variant-numeric: tabular-nums;
}

/* Exactly the red `FieldError` renders, by reference rather than by copy: it
   uses the class `text-red-600`, which under Tailwind v4 is this token —
   `oklch(57.7% 0.245 27.325)`, visibly higher-chroma than v3's `#dc2626`. Both
   would otherwise sit on the same over-limit field in two different reds. */
.character-counter.is-over {
  color: var(--color-red-600, oklch(57.7% 0.245 27.325));
  font-weight: 600;
}

/* Hero variant. `HeroDisplay` paints the section with a 60%-black gradient over
   the photo, so the default mid-grey is unreadable there and `.hero p`'s
   `font-light` leaves 12px of hairline text on an image.
   The polarity follows the house rule: the light-surface treatment is the
   default so a new light surface is safe without anyone remembering, and the
   hero opts in — the same way round as `.image-uploader-modal-trigger`. */
.hero .character-counter {
  color: rgba(255, 255, 255, 0.85);
  font-weight: 400;
}

/* The over-limit state is the one that must be seen, and red-600 is a mid-tone
   that reads *worse* than the grey it replaces against a darkened photo. A pale
   red keeps both the meaning and the contrast. A literal, not a token: Tailwind
   emits `--color-red-300` only where a utility uses it, and none does. */
.hero .character-counter.is-over {
  color: #fca5a5;
  font-weight: 600;
}

/* Field errors inside the hero, including the over-limit message the counter
   above belongs to. `.field-error` carries no styling of its own — the card
   modals pair it with Tailwind's `text-red-600` — so left alone in the hero it
   inherits `.hero p`'s `mb-8 text-xl font-light` and the section's white: an
   error rendered as 20px of light body copy with 32px of dead space beneath it,
   indistinguishable from the description above it.
   Same pale red and same polarity as the counter, for the same reason — red-600
   is a mid-tone that reads *worse* than white against a darkened photo. The
   mobile drawer is portalled to <body> and so never matches `.hero`; it keeps
   `.hero-edit-drawer .field-error`'s dark red on its white sheet. */
.hero .field-error {
  margin: 0.25rem 0 0;
  font-size: 0.75rem;
  line-height: 1rem;
  font-weight: 600;
  color: #fca5a5;
}

/* ---------------------------------------------------------------------------
   The onboarding resume card (MM-156)

   Editor chrome, rendered in the band between `EditorNavbar` and
   `.landing-page-preview` — never inside the preview, which is what a visitor
   sees.

   BRINGS ITS OWN GROUND, SO IT KEEPS FIXED INK. That is MM-205's actual
   dividing line, and it is the case of chrome that brings its own ground rather
   than the `.nav-rollup__trigger` one: a surface painting its own background is
   chrome laid over the preview, and a themed ink on a fixed ground is the
   unsatisfiable pair MM-212 named, pointing the other way. Every colour here is
   therefore a literal, deliberately, and stays legible across all 39 themes
   with no theming work.

   Nothing here belongs in `site.css`: a visitor never sees this card, and a
   copy taken into that file would be a section rule for a surface that has no
   public counterpart.
   --------------------------------------------------------------------------- */
.editor-resume-card {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px 16px;
    margin: 12px 16px;
    padding: 14px 16px;
    border: 1px dashed #c7d2fe;
    border-radius: 12px;
    background: #eef2ff;
    color: #312e81;
    font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.editor-resume-card__text { min-width: 0; }

.editor-resume-card__title {
    margin: 0;
    font-size: 14px;
    font-weight: 600;
    color: #312e81;
}

.editor-resume-card__detail {
    margin: 2px 0 0;
    font-size: 13px;
    color: #4338ca;
}

.editor-resume-card__actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.editor-resume-card__cta {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    border-radius: 999px;
    background: #4f46e5;
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    white-space: nowrap;
    transition: background-color 120ms ease;
}

.editor-resume-card__cta:hover { background: #4338ca; color: #fff; }
.editor-resume-card__cta svg { width: 15px; height: 15px; }

.editor-resume-card__dismiss {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 28px, above WCAG 2.5.8's 24px minimum without depending on the icon's
       own size — the MM-213 rule. */
    width: 28px;
    height: 28px;
    border-radius: 999px;
    color: #4338ca;
    background: transparent;
    transition: background-color 120ms ease;
}

.editor-resume-card__dismiss:hover { background: #e0e7ff; }
.editor-resume-card__dismiss svg { width: 16px; height: 16px; }

/* ── Focal point picker (add-image-focal-point) ──────────────────────────
   Editor chrome on its own white dialog ground, so fixed ink throughout
   (MM-205's rule): it never takes the teacher's theme tokens.

   The surface shrink-wraps the image. The picker maps a press onto the
   surface's box, so letterboxing inside it would put the point somewhere the
   teacher did not press. */
.focal-point-picker__stage {
    display: flex;
    justify-content: center;
    margin-top: 1rem;
    border-radius: 8px;
    background: #f3f4f6;
}

.focal-point-picker__surface {
    position: relative;
    display: inline-block;
    line-height: 0;
    cursor: crosshair;
    touch-action: none;
    user-select: none;
}

.focal-point-picker__image {
    display: block;
    max-width: 100%;
    max-height: 45vh;
}

.focal-point-picker__marker {
    position: absolute;
    width: 28px;
    height: 28px;
    border: 3px solid #fff;
    border-radius: 50%;
    background: rgba(79, 70, 229, 0.35);
    box-shadow: 0 0 0 1px rgba(17, 24, 39, 0.6), 0 2px 6px rgba(17, 24, 39, 0.4);
    transform: translate(-50%, -50%);
    cursor: grab;
}

.focal-point-picker__marker:focus-visible {
    outline: 3px solid #4f46e5;
    outline-offset: 3px;
}

.focal-point-picker__previews {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 1rem;
    margin-top: 1rem;
}

.focal-point-picker__preview {
    margin: 0;
}

.focal-point-picker__preview figcaption {
    margin-top: 0.25rem;
    font-size: 0.75rem;
    color: #4b5563;
}

.focal-point-picker__frame {
    overflow: hidden;
    background: #e5e7eb;
}

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

.focal-point-picker__actions {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 1.25rem;
}
