/* =========================================================================
   notifications.css — shared notification bell + panel
   -------------------------------------------------------------------------
   Extracted verbatim from account.html's inline <style> (the .notif-* block
   that sat between .btn-icon and .sidebar-trigger) so index.html and
   request.html can show the same bell without a third and fourth copy of
   these rules drifting apart. Every original comment is preserved below —
   they document real bug history and are worth keeping with the code they
   explain. Nothing in the ORIGINAL block was changed; everything new lives
   in the clearly-marked sections at the bottom of this file.

   ONE layout context, as of 4 Sep 2026. All four pages that show the bell
   put it inside a .header-actions flex row: account.html and admin.html
   always did, and index.html and request.html joined them when their
   corner controls were gathered into a row of their own. .notif-bell-wrap
   stays position:relative and flows with its siblings everywhere - except
   below 780px, where it goes static so the panel can anchor to the row
   instead of the button (see that rule's own comment).

   There used to be a second context: a .notif-floating modifier that made
   the bell an independently position:fixed corner control on index and
   request, with its own --notif-right offset, a compensating negative
   `right` on the panel, a mobile bump, a standalone-mode top, and a
   sibling rule that shoved .user-chip-wrap aside to clear it. All of it
   went with the refactor - no page sets the class any more. It was
   deleted rather than left dormant: those `right` offsets assume a fixed
   control sliding along a corner rail, and would now displace a
   position:relative flex child instead.

   Load order does not matter here - every rule in this file that overrides
   a page rule does so on specificity - so a <link> in <head> is fine and
   gets the stylesheet cached across page loads.
   ========================================================================= */

  /* ---------- Tokens this file needs that only account.html defines ----------
     CAUGHT IN TESTING, and it was invisible in review: the unread-count
     badge renders --down-strong, and the "on" state of the push button
     renders --ok-strong. Both tokens are declared in account.html's own
     stylesheet and in NEITHER index.html nor request.html — those two pages
     never had a control that needed them. Moving the bell onto them
     therefore produced a badge with no background at all: the var()
     resolved to nothing, background fell back to transparent, and the
     unread count rendered as faint white-on-white. Perfectly legible on
     account.html, effectively missing on the two pages the whole change
     exists to serve.

     Declaring them here, rather than adding them to two more page
     stylesheets, keeps this file self-sufficient: it now carries every
     token it references, so it cannot be broken again by being linked
     from a page that happens not to define one.

     Values, selectors and cascade order are copied EXACTLY from
     account.html — same three-way structure (light default, system-dark
     that respects an explicit light choice, explicit dark from the
     toggle). Because this stylesheet is linked ahead of every page's own
     inline <style>, account.html's identical declarations still win there
     by source order. Nothing about that page changes; the other two
     simply stop missing a value.

     The originals' own note on why the -strong variants exist at all,
     since it is the reason a plain --down would be wrong here: small
     bold white text on --down measures below the 4.5:1 these sizes need
     in both themes, so these darker pairs (verified at 5.6:1 or better)
     are the ones safe to put white text on. */
  :root {
    --ok-strong: #2f7451;
    --down-strong: #a44840;
  }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --ok-strong: #306a4c;
      --down-strong: #864f4a;
    }
  }
  html[data-theme="dark"] {
    --ok-strong: #306a4c;
    --down-strong: #864f4a;
  }

  .notif-bell-wrap { position: relative; }
  .notif-bell-btn {
    width: 38px; height: 38px; flex: none;
    border-radius: 50%;
    /* Glass, matching the home/request pages' theme-toggle treatment —
       this and .theme-toggle are the only two floating corner buttons
       on this page now, so they should read as the same material. */
    position: relative;
    /* background: none / border: transparent — Carson's site-wide ask
       after confirming the blur/color split looks identical to the
       combined approach. Visible fill/border moved to the new
       .notif-bell-btn-fill child below; box-shadow stays here since
       it's not part of the 2400ms color transition either. border
       stays 1px transparent (border-box) to preserve this element's
       exact dimensions. */
    background: none;
    backdrop-filter: blur(16px) saturate(1.5);
    -webkit-backdrop-filter: blur(16px) saturate(1.5);
    border: 1px solid transparent;
    color: var(--text);
    display: flex; align-items: center; justify-content: center;
    cursor: pointer;
    /* Light-respecting shadow, same technique as index.html/
       request.html — see .getting-started-flip-pane on index.html
       for the full reasoning.

       Own measured --own-shadow-x/-y first, the shared
       --btn-tr-shadow-x/-y only as a fallback. The shared pair is
       computed for the fixed point (innerWidth - 18, 18), and this
       button is not there - it sits mid-row among its neighbours. A
       page that measures it (only /account, as of 4 Sep 2026) gets its
       real direction; a page that does not keeps exactly what it had.

       Renamed from --shadow-x/-y along with the glint above. That one
       had actually collided; this one had not - no page sets
       --shadow-x at :root today - but it is the same generic name in
       the same shared file, one page-level declaration away from the
       same bug, and finding out the second time would be no more
       pleasant than the first. */
    box-shadow: var(--own-shadow-x, var(--btn-tr-shadow-x, 0px)) var(--own-shadow-y, var(--btn-tr-shadow-y, 7px)) 22px rgba(0, 0, 0, calc(0.20 * (1 - var(--frost-p, 0))));
    /* Carson's report: the top-frost-bar's own appearance (a 0.3s
       opacity fade) reads as a "stepping" animation rather than one
       smooth motion when scrolling past the reveal threshold — traced
       to html.frost-scrolled's own box-shadow:none rule (further down)
       having nothing to transition through on this element at all,
       unlike .theme-toggle's matching rule, which already declares
       box-shadow 100ms. This element's shadow was snapping to nothing
       instantly while the bar itself was still mid-fade, a beat later
       — genuinely two different motions at once, not one. Matches
       .theme-toggle's own 100ms so every button affected by the same
       frost-scrolled moment moves together.

       Superseded but kept for the history: that box-shadow:none rule
       no longer exists — this shadow's own alpha rides --frost-p
       continuously instead (see the "Flush with the glass" comment
       further up), so there's no snap left for this transition to
       smooth over. It stays on as a short tail on that continuous
       value, and for the :active press. */
    transition: box-shadow 100ms ease;
  }
  .notif-bell-btn-fill {
    position: absolute;
    inset: 0;
    /* Negative, not left at auto/0 — a position:absolute element with
       default z-index still paints ABOVE normal-flow, non-positioned
       siblings (the bell icon svg, the notification badge) regardless
       of DOM order — see .stat-widget-fill's own copy of this same
       comment for the full reasoning. */
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: rgba(var(--surface-rgb), 0.6);
    border: 1px solid var(--border);
    transition: background-color 2400ms ease, border-color 2400ms ease;
  }
  .notif-bell-btn::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    /* Sparkle: tight, bright hot-spot layer + background-blend-
       mode:screen on top of the original glint below — same
       technique as index.html's own copy of this.

       --own-glint-angle, NOT --glint-angle, and the name is the whole
       point. This file is loaded by index.html, request.html,
       account.html and admin.html. Two of those set --glint-angle on
       :root - it is the credits panel's angle there - and custom
       properties inherit, so a rule here reading --glint-angle picks up
       whatever that page happened to mean by it.

       That is not hypothetical. #130 shipped exactly that rule and the
       bell on / and /request immediately started rendering at the
       credits panel's 260.4deg instead of the 176.1deg it had before;
       its own angle is 209.7deg, so the change made it worse. The PR
       claimed those pages never set the property, and verified it on
       /account - the one page where that happens to be true.

       A generic name is safe in a page's own stylesheet and unsafe in a
       shared one. --own-* says "set on this element, by measurement",
       and nothing sets it at :root anywhere. */
    background:
      linear-gradient(var(--own-glint-angle, var(--btn-tr-glint-angle, 180deg)),
        rgba(255, 255, 255, 1) 0%,
        rgba(255, 255, 255, 0.85) 1.5%,
        rgba(255, 255, 255, 0.3) 4%,
        transparent 9%),
      linear-gradient(var(--own-glint-angle, var(--btn-tr-glint-angle, 180deg)),
      rgba(255, 255, 255, 0.9) 0%,
      rgba(var(--light-rgb), 0.45) 22%,
      transparent 48%);
    background-blend-mode: screen, normal;
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    pointer-events: none;
  }
  .notif-bell-btn svg { width: 17px; height: 17px; }
  .notif-bell-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

  .notif-badge {
    position: absolute;
    top: -3px; right: -3px;
    min-width: 17px; height: 17px; padding: 0 4px;
    border-radius: 999px;
    /* --down-strong, not --down — same contrast fix as the pill-button
       harmonization pass (see --ok-strong's own definition for the
       full math): 10px bold white text on --down measures below the
       4.5:1 this size needs, in both themes. */
    background: var(--down-strong); color: #fff;
    font-size: 10px; font-weight: 700;
    border: 2px solid var(--bg);
    /* Flexbox centering instead of line-height: line-height equal to the
       full box height doesn't actually center the text once a border is
       present (box-sizing:border-box eats into the content box the text
       sits in, but line-height didn't shrink to match) — that mismatch
       is what pushed the number visibly off-center. Flex centering has
       no such gap; it centers the actual content area regardless of
       border/padding. */
    display: flex; align-items: center; justify-content: center;
  }
  .notif-badge[hidden] { display: none; }

  .notif-panel {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    width: min(340px, calc(100vw - 36px));
    /* Frosted glass, same values as .top-frost-bar.
       background: none / border: transparent — Carson's site-wide ask
       after confirming the blur/color split looks identical to the
       combined approach. Visible fill/border moved to the new
       .notif-panel-fill child below; this element keeps only the
       static blur + its own entrance animation (transform/opacity,
       unrelated to color). border stays 1px transparent (border-box)
       to preserve this element's exact dimensions. */
    background: none;
    backdrop-filter: blur(14px) saturate(1.4);
    -webkit-backdrop-filter: blur(14px) saturate(1.4);
    border: 1px solid transparent;
    border-radius: 14px;
    /* Light-respecting shadow, own dedicated live-measurement function
       (setNotifPanelShadow, near this element's other JS) since this
       element has no existing glint-angle tracking to piggyback on.
       Reach/blur roughly matches the old flat recipe's own weight
       (16px/40px), between the small buttons and the full panels. */
    box-shadow: var(--shadow-x, 0px) var(--shadow-y, 14px) 40px rgba(0, 0, 0, 0.20);
    transform: translateY(-6px) scale(0.97);
    opacity: 0;
    pointer-events: none;
    /* background-color/border-color moved to .notif-panel-fill along
       with the properties they animate — everything left here is
       this element's own entrance/positioning animation, unrelated
       to color. */
    transition: transform 0.18s cubic-bezier(.22,.61,.36,1), opacity 0.15s ease;
    overflow: hidden;
  }
  .notif-panel-fill {
    position: absolute;
    inset: 0;
    /* Negative, not left at auto/0 — a position:absolute element with
       default z-index still paints ABOVE normal-flow, non-positioned
       siblings (the header/list) regardless of DOM order — see
       .stat-widget-fill's own copy of this same comment for the full
       reasoning. */
    z-index: -1;
    pointer-events: none;
    border-radius: inherit;
    background: rgba(var(--bg-rgb), 0.4);
    border: 1px solid var(--border);
    transition: background-color 2400ms ease, border-color 2400ms ease;
  }
  .notif-panel.open { transform: translateY(0) scale(1); opacity: 1; pointer-events: auto; }
  @media (prefers-reduced-motion: reduce) {
    /* transform/opacity (the entrance animation) are the only
       transitions left on this element post-split — background-color/
       border-color no longer live here at all. The fill layer's own
       2400ms theme fade is untouched by this selector entirely,
       exactly as intended. */
    .notif-panel { transition: none; }
  }

  .notif-panel-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 12px 14px; border-bottom: 1px solid var(--border);
  }
  .notif-panel-head h4 { font-size: 13.5px; font-weight: 650; margin: 0; }
  .notif-clear-all-btn {
    background: var(--surface); border: 1px solid var(--border); color: var(--accent);
    font-size: 12px; font-weight: 650; cursor: pointer; padding: 5px 12px;
    border-radius: 999px;
    transition: transform 0.15s ease, background-color 2400ms ease, border-color 2400ms ease, color 2400ms ease;
  }
  .notif-clear-all-btn:hover:not(:disabled) { transform: translateY(-1px); }
  .notif-clear-all-btn:disabled { color: var(--text-dim); cursor: default; }

  .notif-list { max-height: 320px; overflow-y: auto; }
  .notif-item {
    display: flex; gap: 10px;
    padding: 12px 14px; border-bottom: 1px solid var(--border);
  }
  .notif-item:last-child { border-bottom: none; }
  .notif-item.unread { background: var(--accent-soft); }
  .notif-item-icon {
    width: 30px; height: 30px; border-radius: 8px; flex: none;
    background: var(--surface); border: 1px solid var(--border); color: var(--accent);
    display: flex; align-items: center; justify-content: center;
  }
  .notif-item-icon svg { width: 15px; height: 15px; }
  .notif-item-body { min-width: 0; flex: 1; }
  .notif-item-app { font-size: 10.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--accent); }
  .notif-item-subject { font-size: 13px; font-weight: 600; line-height: 1.35; margin: 2px 0; }
  .notif-item-time { font-size: 11.5px; color: var(--text-dim); }

  .notif-empty { padding: 32px 14px; text-align: center; font-size: 13px; color: var(--text-dim); }

  /* =======================================================================
     NEW — everything below this line is additive. The block above is
     account.html's original CSS, moved unchanged.
     ======================================================================= */

  /* A .notif-floating modifier lived here until 4 Sep 2026: it made
     .notif-bell-wrap an independently position:fixed corner control on
     index.html and request.html, which had no header row for it to sit
     in. Those pages now gather their corner controls into a
     .header-actions flex row like account.html and admin.html, so the
     base rules above are correct on all four and the modifier had no
     remaining user. Its --notif-right variable, its panel offset, its
     mobile bump, its standalone-mode top and its sibling rule against
     .user-chip-wrap all went with it. */
  .notif-bell-wrap[hidden] { display: none; }

  @media (max-width: 780px) {
    /* 38 -> 48px touch target, matching what .theme-toggle and the other
       corner controls already grow to at this breakpoint on all four
       pages. account.html's own mobile rule already resizes
       .notif-bell-btn (it's listed alongside .theme-toggle there), so on
       that page this is a no-op repeat of the same values rather than a
       second, divergent number - which is the point of writing it
       unconditionally. index.html, request.html and admin.html have no
       such rule of their own and take their 48px from here. */
    .notif-bell-btn {
      width: 48px;
      height: 48px;
    }
    .notif-bell-btn svg {
      width: 21px;
      height: 21px;
    }
    /* ---------- Notification panel, kept on-screen ----------
       Moved here from account.css on 4 Sep 2026 - unchanged in effect,
       but it is no longer one page's problem. Carson's original report
       was against /account: opening the bell there expanded the panel
       off the left edge of the screen.

       .notif-panel is position:absolute, right:0, width min(340px,
       calc(100vw - 36px)) - it hangs from the RIGHT EDGE OF ITS
       CONTAINING BLOCK and grows leftward. .notif-bell-wrap is
       position:relative by default, so that containing block is the bell
       itself, and the bell is not the rightmost control in the row - the
       theme toggle and the chip (or Sign out) sit to its right. Measured
       on /account at 390px, the bell's right edge sat 134px in from the
       viewport's, so a 340px panel started at -84px. Off-screen, exactly
       as reported. Desktop never showed it only because there was enough
       width to absorb the same misalignment.

       Taking the wrap off position:relative hands the panel the row as
       its containing block instead. .header-actions is fixed at
       right:18px, so right:0 resolves to 18px from the viewport edge -
       arrived at structurally, with no compensating arithmetic of our
       own to keep correct if the controls beside the bell ever change
       size, count or label text.

       index.html and request.html used to dodge this entirely: their
       .notif-floating modifier made the bell its own fixed corner
       control at right: var(--notif-right), and paid that offset back
       out again with a deliberately negative `right` on the panel. Both
       halves are gone, and those two pages now use the same
       .header-actions row as the other two - so they inherit the same
       containing block, the same bug, and now the same fix.

       Safe to move off position:relative: the only other absolutely
       positioned things in this component are .notif-badge and
       .notif-bell-btn-fill/::before, and all of them resolve against
       .notif-bell-btn (which sets its own position:relative), not
       against this wrap. .notif-panel's own `top: calc(100% + 10px)` is
       likewise unaffected - .header-actions is align-items:center with
       no padding, so its height is exactly the tallest control in it,
       the 48px bell, and 100% resolves to what it did before.

       Scoped to this breakpoint because that is where the panel can
       genuinely run off-screen (below ~474px at today's button sizes)
       and where Carson reported it; above 780px the desktop header keeps
       its existing under-the-bell alignment. */
    .header-actions .notif-bell-wrap {
      position: static;
    }
  }

  /* ---------- Push notifications footer ----------
     Lives inside the panel rather than in account.html's preferences
     card, deliberately: the panel is the one piece of UI that now exists
     identically on all four pages, so enabling push is reachable from
     wherever the user happens to notice they want it. account.html's
     Notification Emails preferences are unaffected and stay where they
     are — this controls a different transport, not the same setting.

     Hidden entirely (via [hidden] on the element, set by JS) when the
     browser has no Push API at all, so unsupported browsers see the
     panel exactly as it looks today rather than a dead control. */
  .notif-push-foot {
    border-top: 1px solid var(--border);
    padding: 11px 14px 12px;
    display: flex;
    align-items: center;
    gap: 10px;
    transition: border-color 2400ms ease;
  }
  .notif-push-foot[hidden] { display: none; }
  .notif-push-foot-text { min-width: 0; flex: 1; }
  .notif-push-foot-title {
    font-size: 12.5px;
    font-weight: 650;
    line-height: 1.3;
  }
  .notif-push-foot-sub {
    font-size: 11.5px;
    color: var(--text-dim);
    line-height: 1.35;
    margin-top: 2px;
  }
  /* Same pill shape and material as .notif-clear-all-btn above so the panel's two
     buttons read as one family, with the 2400ms color timings this
     site's theme transition standardizes on for fills and borders. */
  .notif-push-btn {
    flex: none;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--accent);
    font-size: 12px;
    font-weight: 650;
    cursor: pointer;
    padding: 6px 13px;
    border-radius: 999px;
    transition: transform 0.15s ease, background-color 2400ms ease,
                border-color 2400ms ease, color 2400ms ease, opacity 0.15s ease;
  }
  .notif-push-btn:hover:not(:disabled) { transform: translateY(-1px); }
  .notif-push-btn:disabled { color: var(--text-dim); cursor: default; opacity: 0.75; }
  .notif-push-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  /* Enabled state — reads as "on" rather than as another call to action.
     --ok-strong (not --ok) for the same contrast reason .notif-badge uses
     --down-strong: small bold text on the softer token measures below
     4.5:1 in both themes. */
  .notif-push-btn.is-on {
    color: var(--ok-strong, var(--ok, var(--accent)));
  }
  @media (prefers-reduced-motion: reduce) {
    .notif-push-btn:hover:not(:disabled) { transform: none; }
  }

  /* ---------- First-launch push primer ----------
     Carson's ask, 2026-08-21: an automatic prompt on first PWA launch
     rather than requiring the user to find the panel footer's Enable
     button themselves. See the long comment on maybeShowPushPrimer() in
     notifications.js for the full platform reasoning — this is a custom
     in-app ask, shown at most once, whose own Enable button is what
     actually triggers the real system permission dialog.

     Deliberately its own small, centered card rather than reusing
     .notif-panel — this needs to work the moment the page loads, before
     the user has touched the bell at all, and a centered modal reads as
     an introduction in a way a dropdown anchored to an as-yet-unnoticed
     button would not. */
  .notif-primer-overlay {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding: 0 16px calc(24px + env(safe-area-inset-bottom, 0px));
    background: rgba(0, 0, 0, 0.28);
    animation: notifPrimerFadeIn 0.2s ease;
  }
  .notif-primer-overlay[hidden] { display: none; }
  @keyframes notifPrimerFadeIn {
    from { opacity: 0; }
    to { opacity: 1; }
  }
  .notif-primer-card {
    width: min(360px, 100%);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 20px 18px 16px;
    box-shadow: 0px 14px 40px rgba(0, 0, 0, 0.28);
    text-align: center;
    animation: notifPrimerRise 0.22s cubic-bezier(.22,.61,.36,1);
  }
  @keyframes notifPrimerRise {
    from { transform: translateY(16px); opacity: 0; }
    to { transform: translateY(0); opacity: 1; }
  }
  @media (prefers-reduced-motion: reduce) {
    .notif-primer-overlay, .notif-primer-card { animation: none; }
  }
  .notif-primer-icon {
    width: 46px; height: 46px;
    margin: 0 auto 12px;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent);
    display: flex; align-items: center; justify-content: center;
  }
  .notif-primer-icon svg { width: 22px; height: 22px; }
  .notif-primer-title {
    font-size: 16px;
    font-weight: 700;
    margin-bottom: 6px;
  }
  .notif-primer-sub {
    font-size: 13px;
    line-height: 1.45;
    color: var(--text-dim);
    margin-bottom: 16px;
  }
  .notif-primer-actions {
    display: flex;
    gap: 10px;
  }
  .notif-primer-actions button {
    flex: 1;
    padding: 11px 0;
    border-radius: 999px;
    font-size: 14px;
    font-weight: 650;
    cursor: pointer;
    transition: transform 0.15s ease, opacity 0.15s ease;
  }
  .notif-primer-actions button:disabled { opacity: 0.6; cursor: default; }
  .notif-primer-actions button:hover:not(:disabled) { transform: translateY(-1px); }
  .notif-primer-later-btn {
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
  }
  .notif-primer-enable-btn {
    background: var(--accent);
    border: 1px solid var(--accent);
    color: #fff;
  }
