/* ==========================================================================
   REGISTRATION MODAL
   Right-anchored full-height panel on desktop; full-screen sheet rising from
   the bottom on mobile. Panel translation only — never scale.
   The page behind dims and blurs but is not hidden: the room stays visible.
   ========================================================================== */

.modal {
  position: fixed;
  inset: 0;
  z-index: var(--layer-modal-scrim);
  visibility: hidden;
  display: flex;
  justify-content: flex-end;
}

.modal.is-active { visibility: visible; }

/* --------------------------------------------------------------------------
   SCRIM
   -------------------------------------------------------------------------- */
.modal__scrim {
  position: absolute;
  inset: 0;
  background-color: var(--color-scrim-modal);
  -webkit-backdrop-filter: blur(var(--blur-scrim));
  backdrop-filter: blur(var(--blur-scrim));
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
  border: 0;
  cursor: pointer;
}

.modal.is-open .modal__scrim { opacity: 1; }

/* --------------------------------------------------------------------------
   PANEL
   0 radius — it is a large container flush to the viewport edge.
   -------------------------------------------------------------------------- */
.modal__panel {
  position: relative;
  z-index: var(--layer-modal-panel);
  inline-size: 100%;
  max-inline-size: var(--modal-panel-max);
  block-size: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  background-color: var(--color-surface-card);
  border-inline-start: var(--border-hairline) solid var(--color-border-default);
  border-radius: var(--radius-none);
  padding: var(--space-lg) var(--container-padding-inline) var(--space-2xl);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  transform: translateY(100%);
  transition: transform var(--duration-modal-out) var(--ease-reveal);
}

.modal.is-open .modal__panel {
  transform: none;
  transition-duration: var(--duration-modal-in);
}

@media (min-width: 768px) {
  .modal__panel {
    transform: translateX(100%);
    padding-block: var(--space-2xl) var(--space-3xl);
  }
}

/* --------------------------------------------------------------------------
   PANEL HEAD
   The seat cell — a single-element reprise of the page motif.
   -------------------------------------------------------------------------- */
.modal__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

/* R15w — THE GROUND IS PERMANENT, by direction. --color-surface-hover was on
   :hover only; it is now the resting state and the control reads as a button
   before it is pointed at.

   ⚠ THE HOVER IS COLOUR-ONLY NOW, and that is forced rather than chosen.
   §9a's ground ladder is bg-base -> surface-02 -> surface-03 and stops there:
   the panel is surface-02 #121212, this box is surface-03 #1A1A1A, and there
   is no fourth step to move to. So the glyph carries the state instead —
   --color-text-body to --color-text-heading, 8.3:1 to 15.9:1 on the box. A
   value change, not a nudge, and it is the same move .btn uses when its
   ground is fixed.

   THE BOX IS 1.09:1 AGAINST THE PANEL, which is the whole ladder's step size
   at this end and is meant to read as a shape rather than as a colour. The
   glyph does the work; §18's 3:1 applies to the glyph and it clears it by
   more than double at rest.

   :focus-visible is deliberately not listed. The green ring already announces
   focus, and adding a second signal to the same event says nothing new. */
.modal__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--tap-target-min);
  block-size: var(--tap-target-min);
  margin-inline-end: calc(var(--space-xs) * -1);
  background-color: var(--color-surface-hover);
  border-radius: var(--radius-control);
  color: var(--color-text-body);
  transition: color var(--duration-base) var(--ease-standard);
}

.modal__close:hover { color: var(--color-text-heading); }

.modal__close svg { inline-size: var(--icon-size-inline); block-size: var(--icon-size-inline); stroke-width: var(--icon-stroke); }

/* Wrapper only. The eyebrow's own type and frame come from the .eyebrow
   component — every eyebrow on the page carries the notched box. */
/* R9v — the eyebrow takes the HERO's treatment, by direction: the
   --emphasis frame with a sand label, over the --filled interior.

   MEASURED on the modal panel (--color-surface-card #121212):
     sand label #F2D98A            13.44:1
     the 20%-sand frame            1.65:1 against the panel — a hairline, and
                                   it reads
     the --filled interior #1A1A1A 1.08:1

   ✅ R21m — THAT WARNING WAS RIGHT AND IS NOW ACTED ON. It stood here saying
   "if the filled look matters in the modal it needs a value derived against
   #121212, not the one derived against #000000", and it did matter: with the
   fill at 1.08:1 the plate simply was not there, so the frame's twelve
   hairline segments read as loose strokes around the label instead of as the
   outline of a shape. That is what "a small stroke at the bottom and right"
   was — the notch's stepped corners with nothing behind them.

   THE FIX GOES DARKER, NOT LIGHTER, AND SPENDS NO NEW COLOUR. The dark ladder
   has nothing above --color-surface-hover, so a lighter fill would have meant
   a new value and a §3 amendment. Black on the #121212 panel is 1.17:1 —
   almost exactly the separation #1A1A1A has on the page's #000000 ground
   (1.21:1), just inverted. The eyebrow reads as one plate again, which is the
   whole point of "exactly as elsewhere".

   PRECEDENT, NOT INVENTION: §10's .speakers__talk-label already fills with
   --color-bg-base to sit on a lighter panel. Same problem, same answer.

   RE-MEASURED on the new fill:
     sand label #F2D98A on #000000   15.1:1  (was 13.44:1 on #121212)
     the 20%-sand frame              unchanged — it outlines the plate now
                                     rather than floating on the panel */
.modal__eyebrow { display: flex; }

/* (0,2,0) against the component's (0,1,0), so every other eyebrow on the page
   keeps --color-surface-hover. Only the one sitting on the modal panel moves. */
.modal__eyebrow .eyebrow--filled::before { background-color: var(--color-bg-base); }

/* ✅ R23y — R21o's LOCAL FIX IS GONE, AND THE COMPONENT DOES IT NOW.
   This block switched off the eyebrow's hairline frame for the modal only,
   because the fill covers it and the leftover antialiasing sliver was visible
   on the panel. The same sliver was on every other --filled eyebrow on the
   page; eyebrow.css now blanks the frame for all of them at (0,2,0), so the
   modal needs no rule of its own. */

/* R9w — H3 -> H4, by direction. §14 specifies "display line at H3"; this is
   one step down the scale and the next legal value — there is nothing between
   them.

     H3   34 / 30 / 26
     H4   26 / 24 / 22

   ⚠ IT CHANGES WHAT THE HEADING IS AT MOBILE. H4 tops out at 22px there,
   which is under WCAG's 24px large-text floor — so on a phone this line is
   NORMAL text. White on the panel is 17.93:1, so nothing fails; it only
   matters if the colour is ever changed.

   The rule is shared with the success state's heading, which moves with it.
   That is correct: they occupy the same slot in the panel. */
.modal__title {
  font-family: var(--font-family-heading);
  font-size: var(--font-size-h4);
  font-weight: var(--font-weight-heading);
  line-height: var(--line-height-heading);
  letter-spacing: var(--letter-spacing-heading);
  color: var(--color-text-heading);
}

.modal__intro {
  font-size: var(--font-size-small);
  line-height: var(--line-height-body);
  color: var(--color-text-body);
}

/* --------------------------------------------------------------------------
   SUCCESS
   The form is replaced in place. No redirect.
   -------------------------------------------------------------------------- */
.modal__success { display: none; }
.modal.is-submitted .modal__form { display: none; }
.modal.is-submitted .modal__success { display: flex; flex-direction: column; gap: var(--space-sm); }

/* --------------------------------------------------------------------------
   PAGE BEHIND
   -------------------------------------------------------------------------- */
[data-page-root][inert] { pointer-events: none; }

/* ==========================================================================
   VIDEO LIGHTBOX — R13g

   A VARIANT, NOT A SECOND COMPONENT. Everything structural is inherited: the
   scrim, the entry/exit transitions, the close control, the focus contract.
   Three things change, and each is forced by the content rather than chosen:

     1. CENTRED, not right-anchored. The registration panel is a drawer
        because a form is a task you work through beside the page. A video is
        a single object you look at, and an object belongs in the middle.
     2. SIZED BY ITS RATIO, not by --modal-panel-max. A 16:9 frame in a
        560px drawer would be 315px tall — a postage stamp with a scrim
        around it.
     3. TRANSLATE ONLY, still never scale (§10). The drawer slides from the
        edge; this rises the same 24px the page's reveals use, because it
        arrives in place rather than from off-frame.
   ========================================================================== */
.modal--video { justify-content: center; align-items: center; }

.modal__panel--video {
  /* R15b — THE RATIO IS A VARIABLE NOW, because the three videos are not all
     the same shape: §6's two testimonials are landscape and §3's montage is a
     YouTube Short, which is 9:16. modal.js writes this from the trigger's
     data-video-ratio and clears it on close; 16/9 is the default here so a
     trigger that says nothing still gets the right box.

     ⚠ IT IS USED TWICE AND BOTH USES ARE REQUIRED. .modal__video takes it as
     `aspect-ratio`; this rule takes it inside the width cap. Setting only the
     first would leave a vertical video in a box sized for a landscape one —
     a 9:16 frame capped at 90vh × 16/9 is nearly three times wider than it
     can ever fill, so the panel would be mostly empty and the video tiny. */
  --video-ratio: 16 / 9;

  inline-size: 100%;
  /* The frame is as wide as the page's own container allows and never wider
     than the viewport can show at its own ratio — 90vh of height back-computed
     to a width, so the panel is never taller than the screen. */
  max-inline-size: min(var(--container-max), calc(90vh * (var(--video-ratio))));
  block-size: auto;
  overflow: visible;
  background-color: transparent;
  border: 0;
  padding: var(--container-padding-inline);
  gap: var(--space-sm);
  transform: translateY(var(--reveal-distance));
}

@media (min-width: 768px) {
  /* The base component flips to translateX at this breakpoint for the
     drawer. A centred lightbox must not, or it enters from the side. */
  .modal__panel--video { transform: translateY(var(--reveal-distance)); }
}

.modal.is-open .modal__panel--video { transform: none; }

/* The close control sits above the frame rather than in a bar beside a title:
   there is no title, and a control overlapping video is a control you cannot
   see against half the frames it sits on. */
.modal--video .modal__bar { justify-content: flex-end; }

/* 16:9 — the ratio a YouTube embed is authored at. Letterboxing inside the
   iframe is YouTube's business; the BOX has to be right or the player pads
   itself and the whole thing drifts off-centre. */
.modal__video {
  position: relative;
  inline-size: 100%;
  aspect-ratio: var(--video-ratio);
  background-color: var(--color-bg-base);
}

/* inset:0 rather than 100%/100% — an iframe with no src has no intrinsic
   size, and absolute positioning is what holds the box open before the
   player loads. */
.modal__frame {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  border: 0;
}

/* ==========================================================================
   DIALOG — 23 October 2026, "Sorry! Registrations closed".
   The video lightbox's centring with a small solid panel: one message, no
   task, so it sits in the middle rather than sliding in as a drawer.

   HIERARCHY, two levels the page already owns and nothing new:
     eyebrow   "Sorry!"               the notched sand box, as every section
     heading   "Registrations closed" Loos at H3 (26/30/34), one step above
                                      the drawer's H4 title — it is the only
                                      thing this dialog says
   Centred on one axis, because it is a statement, not a form to read down.

     - The close control is PINNED to the corner, out of the flow, so the
       message is centred in the panel rather than pushed down by a bar row.
     - A --border-emphasis (2px) --color-accent-identity rule on the top edge: a mark, not text,
       so its 4.95:1 on black is the floor that applies. It ties the dialog
       to the violet the CTA that opened it carries.
     - Width is --modal-panel-max (560), the drawer's own cap, so the dialog
       answers to an existing size. At H3 the heading breaks after
       "Registrations" — a two-line block, centred, by design.
     - TRANSLATE ONLY, never scale (§10).
   ========================================================================== */
.modal--dialog {
  justify-content: center;
  align-items: center;
  padding-inline: var(--container-padding-inline);
}
.modal__panel--dialog {
  max-inline-size: var(--modal-panel-max);
  block-size: auto;
  overflow: visible;
  align-items: center;
  text-align: center;
  border: var(--border-hairline) solid var(--color-border-strong);
  border-block-start: var(--border-emphasis) solid var(--color-accent-identity);
  padding: var(--space-3xl) var(--space-lg) var(--space-2xl);
  gap: var(--space-md);
  /* CLOSING FADES IN PLACE. Reversing the entry would slide the panel down
     24px before it vanished — it read as the close control pushing it away.
     So the closed state fades opacity over --duration-fast (in step with the
     scrim) and holds the transform still, snapping it back to the start
     offset only once the panel is invisible (the delayed 0s transition). */
  opacity: 0;
  transform: translateY(var(--reveal-distance));
  transition:
    opacity var(--duration-fast) var(--ease-standard),
    transform 0s linear var(--duration-fast);
}
@media (min-width: 768px) {
  /* Overrides the drawer's translateX(100%), or it enters from the side. */
  .modal__panel--dialog {
    transform: translateY(var(--reveal-distance));
    padding: var(--space-4xl) var(--space-3xl) var(--space-3xl);
  }
}
/* Opening still rises the reveal distance while it fades in. */
.modal.is-open .modal__panel--dialog {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--duration-modal-in) var(--ease-reveal),
    transform var(--duration-modal-in) var(--ease-reveal);
}

.modal__panel--dialog .modal__close {
  position: absolute;
  inset-block-start: var(--space-xs);
  inset-inline-end: var(--space-xs);
  margin: 0;
}
.modal__panel--dialog .modal__eyebrow { justify-content: center; }
.modal__panel--dialog .modal__title { font-size: var(--font-size-h3); }
