.content-modal {
  position: absolute;
  height: 0%;
  width: 100%;
  top: 100%;
  background: var(--color-light);
  /* background: var(--color-white); */
  overflow: hidden;
  z-index: 99;
  transition: all 0.3s;
  --pane-bg: rgb(252, 245, 229);
}
.dark .content-modal {
  --pane-bg: #1a1612;
}

.content-modal.opened {
  height: 100%;
  top: 0%;
  transition: all 0.5s;
}

.content-modal .keen-slider,
.content-modal .keen-slider__slide {
  -webkit-user-select: text;
  user-select: text;
}

/* Clip the home callout slider. KeenSlider lays slides out side-by-side and
   translates the track horizontally; without clipping, the neighbouring slide's
   edge leaks past the viewport (a stray sliver at the left on mobile/tablet). */
.callouts .keen-slider {
  overflow: hidden;
}

/* Desktop icon rail (≥ lg). Active item highlight is applied in a later task. */
.nav-rail__item span:last-child {
  letter-spacing: 0.02em;
}
.nav-rail .menu-icon {
  width: 30px;
  height: 30px;
}
/* Active section: accent the icon (currentColor) + label, with a subtle
   background and an inset accent bar on the rail edge. */
.nav-rail__item--active {
  color: var(--color-accent);
  background: rgba(255, 255, 255, 0.06);
  box-shadow: inset 3px 0 0 var(--color-accent);
}

/* Desktop (≥ lg): the hamburger drawer slides in from the LEFT (over the rail)
   instead of the mobile off-canvas-right container scroll. The mobile mechanism
   is untouched. sideMenu is the direct child of <nav>; the extra type selector
   beats the base `.invisible` so the panel can be shown here. */
@media (min-width: 1024px) {
  nav > [data-content-target="sideMenu"] {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: 315px;
    visibility: visible;
    background: var(--color-black);
    transform: translateX(-100%);
    transition: transform 0.3s ease;
    z-index: 1000;
    overflow-y: auto;
  }
  nav > [data-content-target="sideMenu"].menu-open-desktop {
    transform: translateX(0);
  }
}

/* Floating scroll buttons (index pane). Sticky, zero-height so they overlay
   content without shifting it; shown by pane_scroll_controller via .is-visible. */
.pane-scroll__nav {
  position: sticky;
  height: 0;
  z-index: 5;
  display: none;
}
/* Only show in the two-pane index layout (not single-content panes). */
.content-modal.has-detail .pane-scroll__nav.is-visible {
  display: block;
}
/* Cancel the pane's p-5 padding so the buttons/fade reach the true edges. */
.pane-scroll__nav--up {
  top: -1.25rem;
}
.pane-scroll__nav--down {
  bottom: -1.25rem;
}
.pane-scroll__btn {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background: var(--color-primary);
  color: #fff;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
  cursor: pointer;
}
.pane-scroll__btn:hover {
  background: color-mix(in srgb, var(--color-primary) 85%, #000);
}
.pane-scroll__nav--up .pane-scroll__btn {
  top: 0.75rem;
}
.pane-scroll__nav--down .pane-scroll__btn {
  bottom: 0.75rem;
}
/* Soft fade so list content dissolves under the buttons. --pane-bg is the
   opaque panel colour (set on .content-modal, theme-aware). */
.pane-scroll__nav::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 72px;
  pointer-events: none;
}
.pane-scroll__nav--up::before {
  top: 0;
  background: linear-gradient(to bottom, var(--pane-bg, #fcf5e5), transparent);
}
.pane-scroll__nav--down::before {
  bottom: 0;
  background: linear-gradient(to top, var(--pane-bg, #fcf5e5), transparent);
}

/* ── Desktop shell (≥ lg): dock the content panel, cross-fade instead of slide ── */
@media (min-width: 1024px) {
  .content-modal {
    position: absolute;
    inset: 0;
    height: 100%;
    top: 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease;
    /* The panel is permanently docked over the home hero here (not a transient
       slide-up like mobile), so it must be fully opaque. `--color-light` is only
       95% opaque; dark mode keeps its opaque `--color-surface` via .dark rule. */
    background: rgb(252, 245, 229);
  }
  .content-modal.opened {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.25s ease;
  }
  /* Single-content pages: comfortable centered measure. */
  .reading-column {
    max-width: 68ch;
    margin-inline: auto;
  }

  /* Event/convocation detail: keep content at a readable measure on wide
     screens rather than stretching cards edge-to-edge. Centered in the detail
     pane; left-aligned next to the section nav on the Convocation page. */
  .event-show__body {
    max-width: 880px;
  }
  .event-show:not(.has-section-nav) .event-show__body {
    margin-inline: auto;
  }

  /* Convocation in-page section nav (only when populated by JS). The nav column
     widens with viewport (capped), and the whole block caps its width so the
     content doesn't stretch into unreadably-long lines on very wide screens. */
  .event-show.has-section-nav {
    display: grid;
    grid-template-columns: clamp(200px, 22%, 300px) 1fr;
    gap: 2rem;
    align-items: start;
    max-width: 1200px;
    margin-inline: auto; /* centered in the stage, matching event detail content */
  }
  .event-show .section-nav { display: none; }
  .event-show.has-section-nav .section-nav {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    position: sticky;
    top: 0.5rem;
  }
  .section-nav__item {
    text-align: left;
    padding: 0.5rem 0.75rem;
    border-radius: 0.5rem;
    color: var(--color-secondary);
    cursor: pointer;
  }
  .section-nav__item:hover { color: var(--color-primary); }
  .section-nav__item--active {
    background: color-mix(in srgb, var(--color-primary) 12%, transparent);
    color: var(--color-primary);
    font-weight: 600;
  }

  /* Home callout: show only the first slide (the verse) on desktop — no rotation.
     The slider is made inert via data-slider-disable-desktop-value. */
  .callouts .keen-slider__slide:not(:first-child) {
    display: none;
  }

  /* The rail's hamburger replaces the home top-right hamburger on desktop.
     Higher specificity than `.menu-icon { display:inline-block }` so it wins. */
  .home-topbar .menu-icon {
    display: none;
  }
}

/* ── Two panes (≥ xl): index | detail side by side ────────────────────────────
   Deliberately a wider breakpoint than the desktop chrome above. Between 1024
   and 1280 you get the nav rail but a single content pane, because a list plus
   a detail in ~1100px leaves both too narrow to read.

   Everything here is about having two panes, so it must stay in step with
   `content#hasTwoPanes` (and the matching checks in slider_controller and
   pane_scroll_controller). Below this width the content area behaves like
   mobile — one pane at a time, slid by keen-slider, with the back button
   visible — which is why hiding the back button lives here and not with the
   chrome. */
@media (min-width: 1280px) {
  /* The rail replaces the in-panel back/menu chrome on desktop. */
  .content-modal [data-content-target="backBtn"],
  .content-modal .content-title .menu-icon {
    display: none;
  }

  /* Default desktop: single content pane fills the stage; empty B is hidden. */
  .content-modal .keen-slider {
    display: grid !important;
    grid-template-columns: 1fr;
    transform: none !important;
    width: 100% !important;
    flex: 1 1 auto;
    min-height: 0;
  }
  .content-modal .keen-slider__slide {
    min-width: 0 !important;
    max-width: none !important;
    transform: none !important;
    min-height: 100%;
  }
  .content-modal:not(.has-detail) .keen-slider__slide[data-content-target="contentB"] {
    display: none;
  }
  /* Two-pane only when a detail exists: index (A) | detail (B). The index column
     grows with viewport width toward ~1/3, with a floor (usable card width) and
     a ceiling (so it never dominates on very wide screens). */
  .content-modal.has-detail .keen-slider {
    grid-template-columns: clamp(300px, 35%, 540px) 1fr;
  }
  .content-modal.has-detail .keen-slider__slide[data-content-target="contentA"] {
    border-right: 1px solid color-mix(in srgb, var(--color-secondary) 25%, transparent);
  }

  /* Index list (Events/Resources) stacks to a single column in the narrow
     index pane — the mobile 2/3-up grid would be cramped here. */
  .content-modal .index-card-grid {
    grid-template-columns: 1fr;
  }

  /* Hide the index pane's scrollbar (replaced by the floating scroll buttons). */
  .content-modal.has-detail [data-content-target="contentA"] {
    scrollbar-width: none;
  }
  .content-modal.has-detail [data-content-target="contentA"]::-webkit-scrollbar {
    width: 0;
    height: 0;
  }

}

/* Registration page: a fixed header/footer with a scrolling middle, so the
   Back/Next/Register buttons never move as steps change length.

   Flexbox, not measurement — computing the form's height on a timer is exactly
   what broke the old slider version (docs/llm/registration-form.md). `sticky`
   was tried and cannot express this: a sticky box only sticks within its
   containing block, so as soon as a step grew past the pane the bar scrolled
   away with the content.

   The pane normally scrolls itself (inline overflow-y:auto, hence !important);
   here the inner .registration-scroll does instead, leaving the button bar
   outside it and therefore immovable. */
/* The track's height is content-driven except on desktop, so on a phone a short
   step (Lodging) let the pane collapse to its content and the button bar sat
   mid-screen with dead space beneath. Give the track a definite height here and
   the slide stretches into it (flex row, default align-items: stretch). */
.content-modal .keen-slider:has(.registration-page) {
  flex: 1 1 auto;
  min-height: 0;
}
.content-modal .keen-slider__slide:has(.registration-page) {
  display: flex;
  flex-direction: column;
  overflow-y: hidden !important;
  /* The bar supplies its own bottom spacing and must reach the pane edge. */
  padding-bottom: 0;
}
.content-modal .keen-slider__slide:has(.registration-page) > turbo-frame,
.registration-page,
.registration-page form,
.registration-page form > [data-controller~="registration"] {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
/* Orientation, so it stays put alongside the page header rather than scrolling
   away with the fields. */
.registration-steps {
  flex: 0 0 auto;
}
.registration-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: 1rem;
}
/* Opaque so the form doesn't read through the bar. --pane-bg is the
   theme-aware panel colour set on .content-modal; the `surface`/`light` tokens
   are only 95% opaque. */
.registration-actions {
  flex: 0 0 auto;
  background: var(--pane-bg, rgb(252, 245, 229));
}
/* The desktop pane grid sizes its single row to content, so a slide holding the
   registration page stopped short of the stage and the button bar floated with
   dead space beneath it. Stretch the row so the slide fills the stage and the
   flex chain above has space to distribute. Scoped with :has() so no other
   pane's layout changes. Only applies where the slider *is* a grid, i.e. the
   two-pane breakpoint; below that the track is a flex slider and the
   `flex: 1 1 auto` rule above does the work. */
@media (min-width: 1280px) {
  .content-modal .keen-slider:has(.registration-page) {
    grid-template-rows: 1fr;
  }
}
