/* ============================================================================
 *  Notifications — the system bar's bell (a drop-UP + badge) AND the shared
 *  row styles used by every notification list (bell,
 *  /account/notifications/all log via .portal-notif-list--roomy, prefs live
 *  preview). The bell lives in the bar's right cluster at ≥768px only — on
 *  phones the tab bar's Alerts slot (badge included) is the whole story.
 *  Loaded globally from base.twig; markup: site/footer.twig + the
 *  portalNotifRows renderer in js/site/notifications-bell.js.
 *  Colors come from the --portal-c-* token layer, so light mode needs no
 *  overrides here — with one exception, the bell's hover colour, which is
 *  scoped per theme because it answers to the CHROME, not to the page
 *  surface the tokens describe.
 * ========================================================================== */

.portal-notif {
    position: relative;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
}

.portal-notif-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 0;
    border-radius: var(--bs-border-radius);
    background: transparent;
    color: inherit;
    font-size: var(--portal-text-sm);
    cursor: pointer;
    transition: color var(--portal-dur-1, 120ms) var(--portal-ease-out, ease);
}

/* Hover/open brightens the glyph — it does NOT paint a plate behind it, the way
   the footer's chrome buttons don't either.
   COLOUR, not opacity: the unread badge is a child of this button, so an
   opacity here would wash the red pill out along with the bell — and dimming
   the bell at rest to make room for a brighten changes how the topbar looks on
   every page (measured: 61px against the style-guide baseline). Colour costs
   nothing at rest, because the button inherits the topbar's text colour and the
   topbar's is not the full-strength ink in either theme.
   :focus-visible is deliberately absent from these selectors — it used to sit
   in the hover rule alongside `outline: none`, which traded the keyboard ring
   away for the plate. With the plate gone, the global teal ring is the ring. */
#site-footer[data-bs-theme="dark"] .portal-notif-btn:hover,
#site-footer[data-bs-theme="dark"] .portal-notif-btn[aria-expanded="true"] {
    color: #fff;
}

#site-footer[data-bs-theme="light"] .portal-notif-btn:hover,
#site-footer[data-bs-theme="light"] .portal-notif-btn[aria-expanded="true"] {
    color: var(--portal-nhrl-purple);
}

/* Unread count pill, top-right of the bell. The ring matches the chrome,
   which is brand purple in both themes. */
.portal-notif-badge {
    position: absolute;
    top: -1px;
    right: -2px;
    min-width: 15px;
    height: 15px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--portal-c-btn-danger-bg);
    color: #fff;
    font-size: var(--portal-label-size-sm);
    font-weight: var(--portal-weight-strong);
    line-height: 15px;
    text-align: center;
    font-variant-numeric: tabular-nums;
    box-shadow: 0 0 0 2px var(--portal-c-brand);
    pointer-events: none;
}

/* The panel — a drop-UP out of the system bar, so the gap sits under it. */
.portal-notif-menu {
    width: 340px;
    max-width: calc(100vw - 1.5rem);
    padding: 0;
    margin-bottom: 0.4rem;
    /* No border-radius here: the shared .dropdown-menu rule sets
       --bs-dropdown-border-radius to the card tier for every menu, and a
       literal here would take the value out of the concentric math the
       items derive from. This was the ONLY menu that looked right before
       that fix, and this line is why. */
    overflow: hidden;
}

.portal-notif-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.6rem 0.85rem;
    border-bottom: 1px solid var(--portal-chrome-divider);
}

.portal-notif-title {
    font-weight: var(--portal-weight-label);
    font-size: var(--portal-text-sm);
    margin-right: auto;
}

/* Unread-only filter chip. */
.portal-notif-filter {
    border: 1px solid var(--portal-c-edge-control, var(--portal-c-border));
    background: transparent;
    color: var(--portal-c-text-muted);
    font-size: var(--portal-text-2xs);
    font-weight: var(--portal-weight-label);
    padding: 0.05rem 0.5rem;
    border-radius: 999px;
    cursor: pointer;
    transition: color var(--portal-dur-1, 120ms), border-color var(--portal-dur-1, 120ms), background-color var(--portal-dur-1, 120ms);
}

.portal-notif-filter:hover {
    color: var(--portal-c-text);
}

.portal-notif-filter.is-on {
    color: var(--portal-c-accent);
    border-color: var(--portal-c-accent-border);
    background: var(--portal-c-accent-muted);
}

.portal-notif-markall {
    border: 0;
    background: transparent;
    color: var(--portal-c-accent);
    font-size: var(--portal-text-xs);
    font-weight: var(--portal-weight-medium);
    padding: 0;
    cursor: pointer;
}

.portal-notif-markall:hover {
    text-decoration: underline;
}

.portal-notif-markall:disabled {
    opacity: 0.4;
    cursor: default;
    text-decoration: none;
}

.portal-notif-settings {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    color: var(--portal-c-text-muted);
    font-size: var(--portal-text-sm);
    flex-shrink: 0;
    transition: color var(--portal-dur-1, 120ms), background-color var(--portal-dur-1, 120ms);
}

.portal-notif-settings:hover {
    color: var(--portal-c-text);
    background: var(--portal-c-chrome-hover);
}

/* Loading / empty / error panel — sits OUTSIDE the role=list element. */
.portal-notif-state .portal-notif-skeletons {
    padding: 0.35rem 0;
}

.portal-notif-skeleton-row {
    display: flex;
    gap: 0.6rem;
    padding: 0.6rem 0.85rem;
}

.portal-notif-skeleton-icon {
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    flex-shrink: 0;
}

.portal-notif-skeleton-lines {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    padding-top: 0.15rem;
}

.portal-notif-skeleton-lines .portal-skeleton {
    display: block;
}

/* Scrollable list. */
.portal-notif-list {
    max-height: 360px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* ── The shared row (bell = compact; log page adds --roomy) ─────────── */
.portal-notif-item {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    width: 100%;
    padding: 0.6rem 0.85rem;
    border: 0;
    border-bottom: 1px solid var(--portal-chrome-divider);
    background: transparent;
    color: inherit;
    text-align: left;
    cursor: pointer;
    transition: background-color var(--portal-dur-1, 120ms) var(--portal-ease-out, ease);
}

.portal-notif-item:last-child {
    border-bottom: 0;
}

.portal-notif-item:hover {
    background: var(--portal-c-row-hover);
}

.portal-notif-item.is-unread {
    background: var(--portal-c-row-selected);
}

.portal-notif-item.is-unread:hover {
    background: var(--portal-c-accent-muted);
}

/* Round category icon. */
.portal-notif-icon {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    background: var(--portal-c-accent-muted);
    color: var(--portal-c-accent);
    font-size: var(--portal-text-xs);
}

.portal-notif-mid {
    min-width: 0;
    flex: 1 1 auto;
}

/* The anchor wraps ONLY the text block; buttons are its siblings. */
.portal-notif-main {
    display: block;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.portal-notif-main:hover {
    color: inherit;
}

.portal-notif-item-title {
    display: block;
    font-size: var(--portal-text-sm);
    font-weight: var(--portal-weight-label);
    line-height: 1.25;
}

.portal-notif-item-text {
    font-size: var(--portal-text-xs);
    opacity: 0.8;
    line-height: 1.3;
    margin-top: 0.1rem;
    /* clamp to two lines */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.portal-notif-item-time {
    display: block;
    font-size: var(--portal-text-2xs);
    opacity: 0.55;
    margin-top: 0.2rem;
    text-transform: uppercase;
    letter-spacing: var(--portal-label-tracking);
}

.portal-notif-item-actions {
    margin-top: 0.3rem;
}

/* Full inbox rows keep the same one-tap snooze without spending a second line
 * on it. The renderer opts into this icon-only sibling; bell rows retain the
 * labelled action because their narrow panel benefits from the explicit copy. */
.portal-notif-inline-action {
    flex: 0 0 auto;
    width: var(--portal-control-h);
    min-width: var(--portal-control-h);
    padding-inline: 0;
}

/* Read/unread toggle — the dot is the button. Read rows reveal a hollow
   ring on hover/focus (always visible on touch, where hover doesn't exist). */
.portal-notif-toggle {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    margin: -0.2rem -0.35rem 0 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    cursor: pointer;
}

.portal-notif-toggle:hover,
.portal-notif-toggle:focus-visible {
    background: var(--portal-c-chrome-hover);
    outline: none;
}

.portal-notif-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--portal-c-btn-danger-bg);
}

/* Read rows: hollow ring, revealed on row hover / keyboard focus. */
.portal-notif-item:not(.is-unread) .portal-notif-toggle {
    opacity: 0;
}

.portal-notif-item:not(.is-unread):hover .portal-notif-toggle,
.portal-notif-item:not(.is-unread):focus-within .portal-notif-toggle {
    opacity: 1;
}

.portal-notif-item:not(.is-unread) .portal-notif-dot {
    background: transparent;
    border: 1px solid var(--portal-c-text-muted);
}

/* Roomy variant — the log page's density (no clamp, larger padding, and the
   page scrolls — not the list). */
.portal-notif-list--roomy {
    max-height: none;
    overflow-y: visible;
}

.portal-notif-list--roomy .portal-notif-item {
    padding: 0.85rem 1rem;
}

.portal-notif-list--roomy .portal-notif-item-title {
    font-size: var(--portal-text-md);
}

.portal-notif-list--roomy .portal-notif-item-text {
    display: block;
    -webkit-line-clamp: unset;
    font-size: var(--portal-text-sm);
}

.portal-notif-list--roomy .portal-notif-item-time {
    font-size: var(--portal-text-xs);
}

.portal-notif-foot {
    display: block;
    padding: 0.6rem 0.85rem;
    text-align: center;
    font-size: var(--portal-text-xs);
    font-weight: var(--portal-weight-medium);
    text-decoration: none;
    color: var(--portal-c-accent);
    border-top: 1px solid var(--portal-chrome-divider);
}

.portal-notif-foot:hover {
    background: var(--portal-c-chrome-hover);
    color: var(--portal-c-accent);
}

/* ── Touch: hover is never the only path; targets grow to 44px ───────── */
@media (hover: none) {
    .portal-notif-item:not(.is-unread) .portal-notif-toggle {
        opacity: 1;
    }

    .portal-notif-toggle {
        width: 2.75rem;
        height: 2.75rem;
        margin: -0.45rem -0.6rem 0 0;
    }
}

/* A tablet can render the desktop system bar while still being operated by a
   finger. The bell's quiet 28px visual size is right for a mouse, but the
   button itself must reach the same 44px touch floor as the phone tab slot. */
@media (pointer: coarse) and (min-width: 768px) {
    .portal-notif-btn {
        width: 2.75rem;
        height: 2.75rem;
    }
}

/* The phone sheet died with the fold (v3.1): the bell renders only where the
   system bar does (≥768px, footer hidden below that), and on phones the tab
   bar's Alerts slot navigates to the notification log instead of opening a
   floating list over a screen that has no room for one. */

/* ── Arrival banners — Apple-style transient slide-ins ─────────────────
   Mounted OUTSIDE the chrome (navigation.twig) so fixed positioning answers
   to the viewport, not to a backdrop-filtered strip. Top-RIGHT, macOS's own
   corner — with the topbar gone the top edge belongs to content, and a
   transient card is the one thing allowed to borrow it. OPAQUE on purpose:
   these cards move, and the house rule is that nothing animating carries a
   blur; the menu surface family gives them the same ink and edge without
   one. Row internals reuse .portal-notif-item — one renderer, one look. */
.portal-notif-banners {
    position: fixed;
    top: calc(var(--portal-safe-top, 0px) + var(--portal-chrome-inset, 0.5rem));
    right: var(--portal-toast-inset, 10px);
    z-index: var(--portal-z-banner, 1978);
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: min(340px, calc(100vw - 1.25rem));
    /* The container spans the corner permanently; only the cards are real. */
    pointer-events: none;
}

.portal-notif-banner {
    pointer-events: auto;
    position: relative;
    display: flex;
    align-items: flex-start;
    background: var(--portal-c-overlay-opaque, #232030);
    border: 1px solid var(--portal-c-border, rgba(255, 255, 255, 0.12));
    border-radius: var(--bs-border-radius-lg);
    box-shadow: var(--portal-c-elev-menu, 0 12px 40px rgba(0, 0, 0, 0.5));
    padding-right: 1.9rem;
    overflow: hidden;
    cursor: pointer;
    /* Enter: slide in from the right edge and fade — translate only, never
       scale (the no-scale-on-glass rule, kept here for consistency even on
       an opaque card). Two-frame class flip in JS starts it. */
    opacity: 0;
    transform: translateX(16px);
    transition: transform var(--portal-dur-3, 240ms) var(--portal-ease-emphasized, ease-out),
                opacity var(--portal-dur-3, 240ms) var(--portal-ease-emphasized, ease-out);
}

.portal-notif-banner.is-in {
    opacity: 1;
    transform: none;
}

.portal-notif-banner.is-leaving {
    opacity: 0;
    transform: translateX(16px);
    transition-duration: var(--portal-dur-2, 200ms);
}

/* The renderer's row inside the card: keep its own padding, kill the list
   affordances that make no sense on a card (hover wash, unread dot column). */
.portal-notif-banner .portal-notif-item {
    flex: 1 1 auto;
    min-width: 0;
    background: transparent;
}

.portal-notif-banner-close {
    position: absolute;
    top: 0.3rem;
    right: 0.3rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: 0;
    border-radius: var(--bs-border-radius-sm);
    background: transparent;
    color: var(--portal-c-text-muted, #a5a2b3);
    font-size: var(--portal-text-sm);
}

.portal-notif-banner-close:hover,
.portal-notif-banner-close:focus-visible {
    color: var(--portal-c-text, #f4f4f4);
}

/* 1ms, never none: the leave path waits on a timeout sized to the
   transition; a banner with no transition still leaves on time, but keep
   the same idiom the alertify motion file documents. */
@media (prefers-reduced-motion: reduce) {
    .portal-notif-banner,
    .portal-notif-banner.is-leaving {
        transition-duration: 1ms;
    }
}
