/* ============================================================
   components/modal.css — dd-modal (dialog + drawer) · P2 s3
   ------------------------------------------------------------
   Token-driven overlay dialog with a centred squircle panel and a
   right-side drawer variant. Behaviour lives in components.js
   (window.ddModal): focus trap, Esc, backdrop close, scroll lock,
   focus restore. Opt in with markup:

     <button data-dd-modal-open="my-modal">open.</button>
     <div class="dd-modal" id="my-modal" hidden>
       <div class="dd-modal__panel" role="dialog" aria-modal="true"
            aria-labelledby="my-modal-title">
         <button class="dd-modal__close" data-dd-modal-close>…</button>
         <h2 class="dd-modal__title" id="my-modal-title">…</h2>
         <div class="dd-modal__body">…</div>
       </div>
     </div>

   Without JS the [hidden] attribute keeps it closed. All motion is
   gated behind prefers-reduced-motion.
   ============================================================ */

.dd-modal {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-5);

    /* No backdrop-filter here: a full-viewport live blur stacked over the
       page's other blur layers (the fixed navbar + glass cards, both
       blur(20px)) defeats layer caching, so the compositor re-rasterizes the
       whole blur chain every frame while open — that's what tanked the
       song/project detail pages (idle jank that cleared on close). The solid
       scrim gives the separation without the per-frame cost. */
    background: var(--scrim);

    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--dur-base) var(--ease-soft),
        visibility var(--dur-base) var(--ease-soft);

    /* Hold the scrim on its own compositor layer while open: when the
       fade ends the layer would otherwise be demoted, and that demotion
       re-raster lands as a hitch exactly as the open animation finishes.
       A solid-color layer needs no texture, and [hidden] costs nothing. */
    will-change: opacity;
}

/* JS removes [hidden] before adding .is-open; this keeps a no-JS
   page from ever painting the overlay. */
.dd-modal[hidden] {
    display: none;
}

.dd-modal.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
}

/* No scroll fade (#201): this is a surface with its own edge — a
   gradient inside it reads as a rendering fault rather than a cue. */
.dd-modal__panel {
    position: relative;
    width: 100%;
    max-width: 560px;
    max-height: calc(100dvh - var(--space-10));
    overflow: auto;

    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: var(--space-8);

    opacity: 0;
    transform: translateY(16px) scale(0.98);
    transition: opacity var(--dur-base) var(--ease-dreamy),
        transform var(--dur-base) var(--ease-dreamy);

    /* Promote to a compositor layer before the slide starts, so the open
       motion never re-rasterizes the panel mid-animation. Costs nothing
       while closed: [hidden] is display:none, no layer exists. */
    will-change: transform, opacity;
}

/* Faint grain to match the soft-dreamy surfaces. */
.dd-modal__panel::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background-image: var(--grain);
    opacity: 0.04;
    pointer-events: none;
    z-index: 0;
}

.dd-modal__panel > * {
    position: relative;
    z-index: 1;
}

.dd-modal.is-open .dd-modal__panel {
    opacity: 1;
    transform: none;
}

.dd-modal__close {
    position: absolute;
    top: var(--space-4);
    right: var(--space-4);
    z-index: 2;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25em;
    height: 2.25em;
    border: none;
    border-radius: var(--radius-pill);
    background: none;
    color: var(--text-muted);
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-soft),
        color var(--dur-fast) var(--ease-soft);
}

.dd-modal__close:hover {
    background: var(--surface-tint);
    color: var(--text);
}

.dd-modal__close:focus-visible {
    outline: var(--focus-outline);
    outline-offset: 2px;
}

.dd-modal__kicker {
    margin-bottom: var(--space-2);
}

.dd-modal__title {
    font-family: var(--font-display);
    font-size: var(--fs-h3);
    line-height: var(--lh-tight);
    color: var(--text);
    margin: 0 var(--space-8) var(--space-4) 0;
}

.dd-modal__body {
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    color: var(--text-muted);
}

.dd-modal__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    justify-content: flex-end;
    margin-top: var(--space-7);
}

/* ---- drawer variant: slides in from the right --------------------- */
.dd-modal--drawer {
    padding: 0;
    align-items: stretch;
    justify-content: flex-end;
}

.dd-modal--drawer .dd-modal__panel {
    max-width: 420px;
    height: 100%;
    max-height: 100%;
    border-radius: var(--radius-lg) 0 0 var(--radius-lg);
    transform: translateX(100%);
}

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

@media (max-width: 480px) {
    .dd-modal--drawer .dd-modal__panel {
        max-width: none;
    }
}

/* ---- ddConfirm: promise-based confirm dialog (JS-created) --------- */
.dd-confirm__panel {
    max-width: 440px;
    text-align: left;
}

.dd-confirm__title {
    font-family: var(--font-display);
    font-size: var(--fs-h4, 20px);
    margin: 0 0 var(--space-3);
    color: var(--text);
}

.dd-confirm__message {
    font-size: var(--fs-small);
    line-height: 1.55;
    color: var(--text-muted);
    margin: 0 0 var(--space-6);
}

.dd-confirm__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-3);
    flex-wrap: wrap;
}

.dd-confirm__ok--danger {
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
}

.dd-confirm__ok--danger:hover,
.dd-confirm__ok--danger:focus-visible {
    background: color-mix(in srgb, var(--danger) 85%, #000);
    border-color: color-mix(in srgb, var(--danger) 85%, #000);
}

/* ---- reduced motion: appear/disappear instantly, no slide --------- */
@media (prefers-reduced-motion: reduce) {

    .dd-modal,
    .dd-modal__panel,
    .dd-modal__close {
        transition: none;
    }

    .dd-modal__panel {
        opacity: 1;
        transform: none;
    }

    .dd-modal--drawer .dd-modal__panel {
        transform: none;
    }
}
