/* ═══════════════════════════════════════════════════════════════════
   SelectPanel — the surface a dropdown, picker or short dialog opens
   onto. Knows nothing about what goes inside it.

   Deliberately built out of the same parts peptide-selector.css uses:
   --color-bg-elevated, --radius-lg, --shadow-lg, the 16px top radius on
   the mobile sheet, the 36x4 handle, the 6px scrollbar. A panel opening
   over a Modal that answered to a different set of numbers would read as
   a second app, and the whole reason this file exists is that those
   numbers were being copied by hand.

   Z-INDEX. Panels sit at --z-modal + 20 because they open on top of a
   Modal, and their backdrop one below that. Toasts are at --z-toast
   (600) and stay above both. Nothing in the side effects feature raises
   one over an open panel any more — the cap is answered in the panel's
   own footer — but the ordering is what any future caller would want,
   and a panel that could cover a toast is a panel that can hide a
   message about itself.

   NAMESPACE. `sp-` here means SelectPanel. share.css also uses `sp-`,
   for the public share page, with about seventy classes of its own
   (sp-hero, sp-toast, sp-foot and so on). There is no collision today —
   the two sets were compared class by class — but this file is loaded on
   every page and share.css is loaded on Settings, which is where the
   consent panel opens. Before adding a class here, check it against
   share.css, and vice versa.
   ═══════════════════════════════════════════════════════════════════ */

.sp-backdrop {
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-modal) + 19);
    background-color: var(--color-overlay);
    opacity: 0;
    transition: opacity var(--duration-normal) var(--ease-out);
}
.sp-backdrop.sp-open { opacity: 1; }

/* Opened over another panel. It still absorbs the tap that closes this
   layer, which is the whole job, but it adds no second wash of dimming
   over a page that a Modal already darkened once. */
.sp-backdrop--nested { background-color: transparent; }

/* On the way out and still on screen. Whatever is underneath is what the
   next tap is for. */
.sp-backdrop.sp-closing,
.sp-panel.sp-closing { pointer-events: none; }

/* Stood aside for a panel that opened on top of it. It has played its
   leaving animation and kept everything else, so this is only about not
   being hit-testable while it is invisible: without `sp-open` the panel
   is already at opacity 0, and `inert` covers the same ground in every
   browser that has it. The BACKDROP deliberately keeps both its class
   and its dimming; see suspend() in select-panel.js. */
.sp-panel.sp-aside { pointer-events: none; }

/* ─── The surface ─────────────────────────────────────────────────── */
/* Flex column with overflow hidden, so a caller can put a sticky search
   at the top and a sticky footer at the bottom with a scrolling middle,
   and the panel's own max-height governs all three. */
.sp-panel {
    position: fixed;
    z-index: calc(var(--z-modal) + 20);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    opacity: 0;
}

/* The body is the caller's box. It is a flex column so a child marked
   `flex: 1` scrolls and the rest stays put; min-height: 0 is what lets
   that child actually shrink instead of pushing the panel open. */
.sp-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* For content that is one block of prose rather than a list with rails. */
.sp-body--scroll {
    display: block;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding: var(--space-5);
}

/* ─── Header ──────────────────────────────────────────────────────── */
.sp-head {
    flex-shrink: 0;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-5) var(--space-5) 0;
}
.sp-head-txt { min-width: 0; }
/* The optional second line in here is .dialog-meta, and it is defined in
   components.css next to .modal-title, because Modal draws the same line in
   its own header (.dd-head-meta). One definition, so a panel opening over a
   sheet reads as the same object. That file is loaded on every page, so the
   dependency is safe; it is the same one .form-label rests on. */
.sp-title {
    display: block;
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    letter-spacing: -0.01em;
    line-height: var(--leading-tight);
    color: var(--color-text-primary);
}
.sp-close {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: -4px -6px 0 0;
    padding: 0;
    border: 0;
    border-radius: var(--radius-md);
    background: none;
    color: var(--color-text-tertiary);
    cursor: pointer;
    transition: var(--transition-colors);
}
.sp-close svg { width: 18px; height: 18px; }
@media (hover: hover) {
    .sp-close:hover { background: var(--color-bg-tertiary); color: var(--color-text-primary); }
}
.sp-close:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* For a panel whose content writes its own heading. Sits over the scroll
   area, so it carries the surface colour rather than letting text run
   underneath it. Content that could reach this far right pads itself
   clear; see .se-cs-t. */
.sp-close--float {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    z-index: 2;
    margin: 0;
    background: var(--color-bg-elevated);
}

/* Context line for a sheet that covers whatever named the thing being
   edited. Doubles as a drag grab area, which is why it has real padding
   rather than a margin. */
.sp-caption {
    flex-shrink: 0;
    margin: 0;
    padding: var(--space-3) var(--space-5) 0;
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    color: var(--color-text-tertiary);
    letter-spacing: 0.01em;
}

/* ─── Anchored (desktop dropdown) ─────────────────────────────────── */
/* left / top / bottom / width come from JS, because the only honest source
   for them is the trigger's measured box. The height it MAY use arrives as
   --sp-room; a component is free to cap it further, which an inline
   max-height would have made impossible. */
.sp-panel--anchor {
    max-height: var(--sp-room, 320px);
    transform: translateY(-4px);
    transition: opacity var(--duration-fast) var(--ease-out),
                transform var(--duration-fast) var(--ease-out);
}
.sp-panel--anchor.sp-open {
    opacity: 1;
    transform: translateY(0);
}
.sp-panel--anchor.sp-panel--up { transform: translateY(4px); }
.sp-panel--anchor.sp-panel--up.sp-open { transform: translateY(0); }

/* Flush against the trigger: one continuous surface with the seam removed,
   which is how .ps-dropdown meets .ps-trigger.ps-open. JS pins the panel at
   exactly the trigger's left edge and width and at zero gap, so all that is
   left here is squaring off the edge they share and dropping the border the
   trigger already draws.

   No slide on this one. Something physically joined to the field should not
   detach itself by four pixels first. */
.sp-panel--merge {
    border-top: 0;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    transform: none;
}
.sp-panel--merge.sp-open { transform: none; }
.sp-panel--merge.sp-panel--up {
    border-top: 1px solid var(--color-border-strong);
    border-bottom: 0;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    transform: none;
}

/* ─── Centred (desktop dialog) ────────────────────────────────────── */
.sp-panel--center {
    top: 50%;
    left: 50%;
    width: min(460px, calc(100vw - var(--space-8)));
    max-height: min(640px, 86vh);
    /* NO BORDER, like .modal. A centred panel is this component playing
       Modal's role, and the one dialog family in the app with a hairline
       around it read as a different kind of object, worst of all when
       sized to cover the very modal it opened from. The border survives
       only on the anchored dropdown, where it is the merge seam with the
       trigger. Sheets dropped theirs for the same reason. */
    border: 0;
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-xl);
    transform: translate(-50%, calc(-50% + 12px)) scale(0.98);
    transition: opacity var(--duration-normal) var(--ease-out),
                transform var(--duration-normal) var(--ease-out);
}
.sp-panel--center.sp-open {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
}

/* ─── Sheet (mobile, any placement) ───────────────────────────────── */
.sp-panel--sheet {
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    width: 100%;
    max-height: 78vh;
    max-height: 78dvh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    /* NO BORDER, not just no bottom one. Every Modal renders as a
       borderless sheet on a phone, and a SelectPanel sheet sitting in the
       same stack with a hairline around it read as a different kind of
       object. The border stays on the anchored dropdown, where it is the
       merge seam with the trigger, and on the centred panel, which floats
       over content on a desktop. */
    border: 0;
    transform: translateY(100%);
    transition: opacity var(--duration-normal) var(--ease-out),
                transform var(--duration-normal) var(--ease-out);
    /* Safe area for the home indicator. On the padding rather than on the
       inner rails, so a sticky footer sits above it instead of under it. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
}
.sp-panel--sheet.sp-open {
    opacity: 1;
    transform: translateY(0);
}
/* Following the finger has to be instant or it feels like syrup. */
.sp-panel--sheet.sp-dragging { transition: none; }

.sp-handle {
    flex-shrink: 0;
    display: flex;
    justify-content: center;
    padding: var(--space-2) 0 0;
    /* Generous invisible target, and touch-action so the browser does not
       claim the gesture for scrolling before the handler sees it. */
    min-height: 20px;
    touch-action: none;
}
.sp-handle::after {
    content: '';
    width: 36px;
    height: 4px;
    border-radius: var(--radius-full);
    background-color: var(--color-border-strong);
}
.sp-panel--sheet .sp-head { padding-top: var(--space-3); }

/* The drag grab areas, and the browser has to be told before it decides a
   downward swipe here was meant as a scroll. The body is deliberately NOT
   one of them: a gesture that could dismiss the sheet or scroll a list of
   142 options has to pick one, and on the list it picks scrolling. */
.sp-panel--sheet .sp-head,
.sp-panel--sheet .sp-caption { touch-action: none; }

/* Transitions collapse rather than transforms getting stripped: the centred
   panel is CENTRED by its transform, so removing it would move the dialog
   instead of calming it down. */
@media (prefers-reduced-motion: reduce) {
    .sp-panel,
    .sp-backdrop { transition-duration: 1ms; }
}
