/**
 * Alerts and toasts
 *
 * An alert is a cream panel with a coloured left rule and dark text. The
 * saturated banner the previous build used put white on #22c55e (2.3:1) and on
 * #E6482E (3.9:1); the rule carries the same information at full legibility.
 *
 * The toast is the replacement for a page-top banner that only appears after a
 * reload. It is bottom-left, announces itself through the existing
 * .ui-announcer live region, and carries an Undo where the action is
 * reversible.
 */

.alert {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    border: 1px solid var(--border-on-light-hairline);
    border-left: 3px solid var(--color-info);
    border-radius: var(--radius-md);
    background: var(--card-face);
    box-shadow: var(--shadow-card);
    color: var(--text-on-light);
    font-size: var(--font-size-base);
}

.alert a {
    color: var(--color-red-dark);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/*
 * Everything beside the icon, as ONE flex item.
 *
 * .alert is a flex row, and five call sites used to put <strong>…</strong><br>
 * and a bare text node straight into it — three flex items, an inert <br>, and
 * a description laid out as a second COLUMN beside its own heading. At 390px
 * the label became an 80px column with the body text in a ribbon next to it.
 */
.alert-body {
    flex: 1 1 auto;
    min-width: 0;
}

.alert-title {
    display: block;
    margin-bottom: var(--space-1);
    font-weight: var(--font-weight-semibold);
}

.alert-description {
    color: var(--text-on-light-muted);
}

/*
 * The alert's own leading icon, not every svg that lands inside one.
 *
 * `.alert svg` overrode the icon component's own size attribute, so
 * renderComponent('icon', ['size' => 18]) drew at 18px everywhere and 20px
 * inside an alert — the component's one job, sizing itself, silently
 * countermanded by where it happened to be. Scoped to direct children so a
 * button or an inline glyph in the body keeps the size it asked for.
 */
.alert-icon,
.alert > svg {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    margin-top: 2px;
}

.alert-sm { padding: var(--space-3) var(--space-4); font-size: var(--font-size-sm); }
.alert-lg { padding: var(--space-5) var(--space-6); }

/* ============================================
   VARIANTS
   ============================================ */

.alert-success,
.alert-success-light {
    border-left-color: var(--color-success-fill);
}

.alert-warning,
.alert-warning-light {
    border-left-color: var(--color-gold);
}

.alert-error,
.alert-danger,
.alert-error-light {
    border-left-color: var(--color-red);
}

.alert-info,
.alert-info-light {
    border-left-color: var(--color-info);
}

/*
 * The icon takes the family colour; the text stays ink so it keeps its ratio.
 *
 * These used to name .alert-icon only — a class styled six times here and
 * emitted by exactly nothing. The views render the shared icon component, which
 * writes "icon icon-alert", so every one of these rules was dead and a success
 * tick and an error mark both came out in body ink. Nothing but a 3px left rule
 * told the two states apart.
 */
.alert-success :is(.alert-icon, .icon) { color: var(--color-success); }
.alert-warning :is(.alert-icon, .icon) { color: var(--color-gold-ink); }
.alert-error :is(.alert-icon, .icon),
.alert-danger :is(.alert-icon, .icon) { color: var(--color-danger); }
.alert-info :is(.alert-icon, .icon) { color: var(--color-info); }

.alert-dismissible {
    padding-right: var(--space-12);
}

.alert-close,
.alert .btn-close {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
}

/* ============================================
   INLINE MESSAGES

   A sentence under a field or beside a row: no panel, no shadow.
   ============================================ */

.inline-message {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--font-size-sm);
}

.inline-message-success { color: var(--surface-success); }
.inline-message-warning { color: var(--surface-warning); }
.inline-message-error { color: var(--surface-danger); }
.inline-message-info { color: var(--surface-info); }

/* ============================================
   TOAST
   ============================================ */

.toast-container {
    position: fixed;
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    max-width: min(420px, calc(100vw - var(--space-8)));
    pointer-events: none;
}

.toast-bottom-left { bottom: var(--space-6); left: var(--space-6); }
.toast-bottom-right { bottom: var(--space-6); right: var(--space-6); }
.toast-bottom-center { bottom: var(--space-6); left: 50%; transform: translateX(-50%); }
.toast-top-left { top: var(--space-6); left: var(--space-6); }
.toast-top-right { top: var(--space-6); right: var(--space-6); }
.toast-top-center { top: var(--space-6); left: 50%; transform: translateX(-50%); }

/*
 * Bootstrap owns .toast too, and it loads on every page.
 *
 * Its component ships `.toast:not(.show){display:none}` — specificity (0,2,0)
 * against this rule's (0,1,0) — so every toast this product raised was
 * display:none and NOBODY HAS EVER SEEN A FLASH MESSAGE. It looked fine in any
 * render without the CDN, which is exactly why it survived: the visual audit
 * serves Bootstrap, and both flash fixtures came out byte-for-byte identical
 * with no toast in either.
 *
 * .toast-container .toast ties Bootstrap's specificity and wins on order, since
 * these layers load after the CDN link in templates/header.php. Renaming the
 * class would be the tidier answer and is a bigger change than this deserves;
 * the container is always present, so the tie-break is reliable.
 */
.toast-container .toast,
.toast {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    border-radius: var(--radius-lg);
    /*
     * The edge is not decoration. This pill is --color-teal-dark on a page
     * that samples rgb(2,47,50) — sampled side by side at 390 they are within
     * a few points of each other, so on any screen where the bottom-left of
     * the viewport is page rather than a cream card, the toast had no visible
     * boundary at all and only the shadow separated it.
     */
    border: 1px solid var(--border-on-dark);
    background: var(--color-teal-dark);
    box-shadow: var(--shadow-modal);
    color: var(--color-cream);
    font-size: var(--font-size-base);
    pointer-events: auto;
    animation: toast-enter var(--duration-base) var(--easing);
}

/*
 * One glyph, three colours, so the kind of news is legible before the words
 * are. These are the three that read against the dark teal surface: the pale
 * red is the only red that does, and the bright green fill is used here as an
 * icon colour rather than a bar because nothing else in the palette carries
 * green onto a dark ground.
 */
.toast-icon {
    flex: 0 0 auto;
}

.toast-success .toast-icon { color: var(--color-success-fill); }
.toast-error .toast-icon { color: var(--color-red-pale); }
.toast-info .toast-icon { color: var(--color-gold); }

.toast a,
.toast .toast-action {
    flex: 0 0 auto;
    color: var(--color-gold);
    font-weight: var(--font-weight-semibold);
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

.toast-message {
    flex: 1 1 auto;
}

/*
 * The progress bar is the timer made visible: it runs the width of the toast
 * over the dismiss duration, and pauses with the timer when the pointer or
 * focus is inside. Its duration is set from the script so the two cannot drift.
 */
.toast-progress {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 2px;
    background: var(--color-gold);
    transform-origin: left center;
    animation: toast-countdown linear forwards;
}

@keyframes toast-countdown {
    from { transform: scaleX(1); }
    to { transform: scaleX(0); }
}

/*
 * An error is not on a clock, so it carries a left edge instead — and the edge
 * is now a supporting signal rather than the only one. It used to be the whole
 * difference between "Pairings created." and "2 could not be delivered."
 */
.toast-error {
    border-left: 3px solid var(--color-red-pale);
}

.toast-close {
    flex: 0 0 auto;
    padding: 0;
    border: none;
    background: none;
    color: var(--color-cream);
    font-size: var(--font-size-lg);
    line-height: 1;
    opacity: 0.7;
    cursor: pointer;
    transition: opacity var(--duration-fast) var(--easing);
}

.toast-close:hover,
.toast-close:focus-visible {
    opacity: 1;
}

.toast-with-progress {
    position: relative;
    overflow: hidden;
}

@keyframes toast-enter {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: none; }
}

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

    /*
     * A countdown conveys its meaning by moving, so there is no still version
     * of it worth keeping. The timer still runs; the bar goes.
     */
    .toast-progress { display: none; }
}

@media (max-width: 640px) {
    .toast-container {
        left: var(--space-4);
        right: var(--space-4);
        bottom: var(--space-4);
        max-width: none;
        transform: none;
    }
}
