/* =================================================================================================
   UPDATES (TL-236): the bell beside the header user chip, its panel and its one toast, and the in-context card.
   Markup: Controls/ucUpdatesBell.ascx (bell, panel and toast), Controls/ucUpdateCard.ascx (card). Behaviour: JavaScript/updates.js.
   There are NO banners (retired 2026-10-14, docs/design/NOTIFICATIONS_TOAST_SPEC.md): the bell is the notifier, the panel the inbox.
   Design: docs/design/USER_NOTIFICATIONS_DESIGN.md revision 2 and docs/design/mockups/notifications_v2.html.

   OWN FILE ON PURPOSE. nav.css and layout.css are shared; nothing here edits them. This file is linked AFTER nav.css and
   components.css (see updates-v1_shared_files.patch.txt) and only adds rules for classes that start with "updates-".
   The two places it touches existing elements are the user chip (.user-panel, to make room for the bell) and the
   standard modal header, close X and footer, which the panel reuses on purpose (NTV_N3_MOVE_MODAL_UX.md section 1).

   Tokens only (theme.css). The warning colour appears ONLY on the bell and its count chip, never on a button.
   ================================================================================================= */

:root {
    /* Toast motion (toast spec 2026-10-14). One place for the duration and easing: a short slide down from under the bell, decelerating. Under
       prefers-reduced-motion there is no slide, only a fade (see the media query by the toast). */
    --updates-toast-duration: 200ms;
    --updates-toast-shift: -8px;
    --updates-ease-toast: cubic-bezier(0.2, 0, 0, 1);       /* quick start, soft landing */
    --updates-pulse-duration: 1.4s;                         /* the bell ring: one, ripples and swing together */
    --updates-badge-pulse-duration: 1.2s;                   /* reduced motion: the badge outline pulse (no movement) */
    --updates-store: #7a5af8;               /* the "store operations" family (the mock-up violet) */
    --updates-bell-size: 30px;
}

/* ---------------------------------------------------------------------------------------------
   The bell (V3): BESIDE the user chip, to its right, its own flat control with the count badge (developer ruling 2026-10-13).
   The markup still sits inside .user-panel (the master keeps one grid cell for the chip), so the chip reserves room for it with a
   margin and the bell is positioned in that room. The chip keeps its own look; the bell has no surface of its own.
   --------------------------------------------------------------------------------------------- */
.updates-bell-wrap { display: contents; }

.user-panel:has(.updates-bell-wrap) {
    position: relative;
    /* the bell's room (its 8px gap and its width), less the measured right-rail extension (appbarAlign.js, layout.css); the chip is held at
       the card's width less that room, so chip + bell together are exactly the rail card's width when it is expanded */
    /* + 7px, not 8: .updates-bell is left: calc(100% + 8px) of the chip's PADDING box, which is 2px narrower than the chip (its 1px
       borders) and starts 1px in, so the bell's left edge lands 7px beyond the chip's right edge. Measured in docs/design/harness/apprail.html. */
    margin-right: calc(var(--updates-bell-size) + 7px - var(--appbar-rr-ext, 0px));
    min-width: max(0px, calc(var(--appbar-rr-w, 0px) - var(--updates-bell-size) - 7px));
    /* Rail expanded: appbarAlign.js sets --appbar-rr-cap to the card's width, so the chip can never be wider than the card less the
       bell's room (a long name ends in an ellipsis instead of pushing the chip's left edge past the card's). Unset: no cap. */
    max-width: calc(var(--appbar-rr-cap, 9999px) - var(--updates-bell-size) - 7px);
}

.updates-bell {
    position: absolute;
    left: calc(100% + 8px);
    top: 50%;
    width: var(--updates-bell-size);
    height: var(--updates-bell-size);
    margin-top: calc(var(--updates-bell-size) / -2);
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: 8px;
    background: transparent;                 /* no white square; the bell is just the glyph on the bar */
    color: var(--brand-on-primary);          /* white on the app bar */
    cursor: pointer;
    font-size: 26px;
    line-height: 1;
}

    .updates-bell:hover { background: rgba(255, 255, 255, .12); }
    .updates-bell:focus-visible { outline: 2px solid var(--brand-on-primary); outline-offset: 2px; }

.updates-bell-icon { width: 1em; height: 1em; display: block; }
.updates-bell-outline { opacity: .92; }
.updates-bell-fill { opacity: .35; }

.updates-count {
    position: absolute;
    top: -6px;
    right: -8px;
    min-width: 18px;
    height: 18px;
    padding: 0 4px;
    border-radius: 99px;
    background: var(--warning);              /* amber, dark text: WHITE on --warning is 2.85:1 and fails for 11px text; --ink on it is 6.3:1 */
    color: var(--ink);
    border: 2px solid var(--brand-on-primary);   /* the white ring separates the badge from the bar */
    box-sizing: border-box;
    display: grid;
    place-items: center;
    font: 700 11px/1 var(--ui-font);
}
    .updates-count[hidden] { display: none; }

/* THE RING (about 1.4s, once per session on the first load with unread items and again whenever the count rises). updates.js adds
   .is-pulse only AFTER the page has settled (the loader gone, the page faded in). Two ripples spread from the bell (the amber warning
   tint, 40% to 0 opacity, scale 1 to 2.2, the second 250ms behind the first) while the bell glyph swings (+-14deg, three swings, each
   smaller). Clearly seen, not frantic: once, and it ends. Under prefers-reduced-motion nothing moves; the count badge gets one
   brighter outline pulse of 1.2s instead (see the media query at the end). */
.updates-ripple {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 2px solid var(--warning);
    box-sizing: border-box;
    opacity: 0;
    pointer-events: none;
}
.updates-bell.is-pulse .updates-ripple { animation: updates-ripple calc(var(--updates-pulse-duration) - .25s) ease-out 1 forwards; }   /* the second one starts .25s late and ends with the swing */
.updates-bell.is-pulse .updates-ripple-2 { animation-delay: .25s; }
.updates-bell.is-pulse .updates-bell-icon {
    transform-origin: 50% 12%;                 /* the swing hangs from the top of the bell */
    animation: updates-swing var(--updates-pulse-duration) ease-in-out 1;
}
@keyframes updates-ripple {
    0% { transform: scale(1); opacity: .4; }
    100% { transform: scale(2.2); opacity: 0; }
}
@keyframes updates-swing {
    0% { transform: rotate(0); }
    8% { transform: rotate(14deg); }
    20% { transform: rotate(-14deg); }
    32% { transform: rotate(9deg); }
    44% { transform: rotate(-9deg); }
    56% { transform: rotate(4deg); }
    68% { transform: rotate(-4deg); }
    80%, 100% { transform: rotate(0); }
}
@keyframes updates-badge-pulse {
    0%, 100% { box-shadow: 0 0 0 0 transparent; filter: none; }
    35%, 65% { box-shadow: 0 0 0 3px var(--brand-on-primary); filter: brightness(1.25); }
}

/* ---------------------------------------------------------------------------------------------
   The panel: a popover under the bell, standard modal header + close X + footer, only the list scrolls.
   --------------------------------------------------------------------------------------------- */
.updates-panel {
    position: absolute;
    right: calc((var(--updates-bell-size) + 8px) * -1);   /* V3: its right edge is the bell's */
    top: calc(100% + 8px);
    z-index: 2100;                          /* inside the appbar's stacking context, as the user flyout */
    width: min(420px, calc(100vw - 32px));
    max-height: min(560px, calc(100vh - 120px));
    max-height: min(560px, calc(100dvh - 120px));   /* dvh: the VISIBLE viewport; 100vh on a phone includes the browser's own bar, so the panel's bottom and its footer fell off screen */
    display: flex;
    flex-direction: column;
    background: var(--surface);
    color: var(--ink);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-3);
    overflow: hidden;
    font-size: 14px;
    text-align: left;
}
    .updates-panel[hidden] { display: none; }

    .updates-panel .modal-header { margin: 0; padding: 12px 14px; flex: none; }
    .updates-panel .modal-header h2 { font-size: 18px; }
    .updates-panel .modal-close { color: var(--ink); line-height: 0; padding: 4px; }
    .updates-panel .modal-body {
        padding: 8px 10px;                 /* the cards sit inside the body with a little air */
        display: flex;
        flex-direction: column;
        gap: 8px;                          /* and a little space between them */
        overflow-y: auto;                  /* the BODY scrolls; the header and the footer stay (flex: none) */
        -webkit-overflow-scrolling: touch;
        touch-action: pan-y;
        flex: 1 1 auto;
        min-height: 72px;
        overscroll-behavior: contain;
    }
    /* STICKY FOOTER: the panel is a column; only .modal-body scrolls (flex 1, overflow auto), the header, the filter row and this footer are flex: none. */
    .updates-panel .modal-footer { margin: 0; padding: 10px 14px; border-top: 1px solid var(--border); flex: none; background: var(--surface); }

/* phones: the panel becomes a sheet under the header, edge to edge with the page gutter */
@media (max-width: 640px) {
    .updates-panel {
        position: fixed;
        left: 16px;
        right: 16px;
        top: calc(var(--appbar-height, 86px) + 6px);
        width: auto;
        max-height: calc(100vh - var(--appbar-height, 86px) - 22px);
        max-height: calc(100dvh - var(--appbar-height, 86px) - 22px);
    }
}

.updates-empty { padding: 20px 14px; color: var(--muted); }
.updates-empty strong { display: block; color: var(--ink); font-weight: 600; margin-bottom: 2px; }

/* ---------------------------------------------------------------------------------------------
   One update (panel row and in-context card share the look): a left bar by style, a small source label, the title, the
   body, one button, links with the new-window mark, and quiet "remind me later" and "dismiss" actions.
   --------------------------------------------------------------------------------------------- */
.updates-item,
.updates-card {
    --updates-bar: var(--brand-primary);
    border-left: 4px solid var(--updates-bar);
}
.updates-style-security { --updates-bar: var(--warning); }
.updates-style-store    { --updates-bar: var(--updates-store); }
.updates-style-company  { --updates-bar: var(--brand-primary); }
.updates-style-billing  { --updates-bar: var(--success); }
.updates-style-tip      { --updates-bar: var(--accent); }
.updates-style-info     { --updates-bar: var(--brand-primary); }

/* a tenant's items take the tenant brand (CBS today; a future tenant is one more rule, no code) */
.updates-item[data-tenant="CBS"],
.updates-card[data-tenant="CBS"] { --updates-bar: var(--cbs-accent, #23953c); }
.updates-card[data-tenant="CBS"] { background-image: linear-gradient(90deg, var(--cbs-bg-tint, #f5f9e8), transparent 55%); }
.updates-item[data-tenant="CBS"] .updates-source,
.updates-card[data-tenant="CBS"] .updates-card-source { color: var(--cbs-accent, #23953c); }

/* A card in the panel list: a soft, style-tinted surface (the style colour at 9% fading to 3% over the surface: tokens only, no new
   colours), a thin 1px edge in a lighter mix of the same colour, and only a 3px hint of the bar on the left. A seen item is quieter
   (3% flat). Contrast of the 12-14px text on the 9% end is in the V2c handoff (lowest 4.6:1). */
.updates-item {
    flex: none;
    padding: 12px 14px;
    border: 1px solid color-mix(in srgb, var(--updates-bar) 22%, var(--border));
    border-left: 3px solid color-mix(in srgb, var(--updates-bar) 45%, var(--surface));
    border-radius: var(--radius);
    background: linear-gradient(135deg, color-mix(in srgb, var(--updates-bar) 9%, var(--surface)) 0%, color-mix(in srgb, var(--updates-bar) 3%, var(--surface)) 100%);
}
.updates-item:not(.is-unread) { background: color-mix(in srgb, var(--updates-bar) 3%, var(--surface)); }
.updates-source,
.updates-card-source {
    font-size: 11px;
    font-weight: 700;
    color: var(--muted);
}

.updates-title,
.updates-card-title { font-weight: 600; }

.updates-text,
.updates-card-text { color: var(--muted); margin: 2px 0 8px; }

.updates-actions,
.updates-card-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }

/* the action button is the standard small secondary button; this only makes it a link-shaped element that fits its label */
.updates-actions .btn,
.updates-card-actions .btn { height: auto; min-height: 26px; padding: 3px 12px; text-decoration: none; }

/* links are the standard .link (theme.css); this class only adds the new-window mark */
.updates-link::after { content: " \2197"; }   /* the new-window mark */

.updates-meta { display: flex; align-items: center; gap: 12px; margin-top: 6px; font-size: 12px; color: var(--muted); }
.updates-meta .updates-when { flex: 1; }
/* "Show more" is the standard .link on a button: only the button chrome is removed */
button.link.updates-more { background: none; border: 0; padding: 2px 0; font: inherit; font-weight: 500; cursor: pointer; }

/* ---------------------------------------------------------------------------------------------
   The in-context card (Controls/ucUpdateCard.ascx): one, dismissible, at the place a feature lives.
   --------------------------------------------------------------------------------------------- */
.updates-card {
    position: relative;
    display: flex;
    gap: 12px;
    margin: 0 0 16px;
    padding: 12px 14px;
    background-color: var(--surface-subtle);
    border: 1px solid var(--border);
    border-left: 4px solid var(--updates-bar);
    border-radius: var(--radius);
}
.updates-card-main { flex: 1; min-width: 0; }
.updates-card-dismiss {
    flex: none;
    align-self: flex-start;
    width: 32px;
    height: 32px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 8px;
    background: none;
    color: var(--muted);
    cursor: pointer;
}
    .updates-card-dismiss:hover, .updates-card-dismiss:focus-visible { color: var(--ink); background: var(--hover-chip); }
.updates-card.is-gone { display: none; }
@media (max-width: 480px) {
    .updates-card { padding: 10px 12px; }
}

/* =================================================================================================
   V2 (TL-236): the warning style, authored HTML bodies and the end-date note.
   ================================================================================================= */
.updates-style-warning { --updates-bar: var(--warning); }

/* an authored (allow-listed) HTML body: paragraphs, lists, links. Long ones fold with a Show more toggle (updates.js). */
.updates-html { color: var(--muted); margin: 2px 0 8px; overflow-wrap: anywhere; }
.updates-html p, .updates-html li { margin: 0 0 6px; font-size: inherit; line-height: inherit; color: inherit; }   /* the page's own p and li rules must not make an authored body bigger than the list around it */
.updates-html p:last-child { margin-bottom: 0; }
.updates-html ul, .updates-html ol { margin: 0 0 6px; padding-left: 20px; }
.updates-html a::after { content: " \2197"; }
.updates-html.is-folded { max-height: 9.5em; overflow: hidden; }
.updates-more { margin: 0 0 8px; }
.updates-ends { color: var(--muted); font-size: 12px; }


/* The BRAND symbol (a store notice's icon): the artwork file is a MASK and the colour is currentColor, so the brand red in the file is
   never used (the mechanism layout.css documents for the nav's brand mark). The url is relative to THIS stylesheet
   (App_Themes/Core -> /Images/Client). One class per brand that has artwork (MT.NoticeIcons.Brands). */
.updates-brand {
    display: inline-block;
    width: 20px;
    height: 20px;
    background-color: currentColor;
    -webkit-mask: center / contain no-repeat;
    mask: center / contain no-repeat;
}
.updates-brand-jl { -webkit-mask-image: url("../../Images/Client/JL_Symbol.svg"); mask-image: url("../../Images/Client/JL_Symbol.svg"); }
.updates-brand-pt { -webkit-mask-image: url("../../Images/Client/PT_Symbol.svg"); mask-image: url("../../Images/Client/PT_Symbol.svg"); }   /* the Precision Tune symbol file is a plain circle: a mask in the brand colour is the same circle */
/* =================================================================================================
   V3 (TL-236, lane U4, 2026-10-14): the bell beside the chip, the panel with groups and filters, the toast, the Notifications page
   and the settings. Decisions: docs/current/NOTIFICATIONS_V3_DECISIONS.md (D11 role-aware categories, D17 "Past 7 days").
   Tokens only; no inline styles; under prefers-reduced-motion there is no slide and no pulse.
   ================================================================================================= */
:root {
    --updates-teal: color-mix(in srgb, var(--accent) 50%, var(--success));   /* billing: a calm teal, no new colour */
}

/* Category colour families: the calm, low-saturation colour a notice is drawn in. Colour is never alone (the icon and the title say it). */
/* THE FAMILIES (designer, 2026-10-14): six hues told apart by hue AND weight. Hue angles (HSL): Security 28 amber, My stores brand
   (Jiffy Lube crimson 342; a Precision Tune user's yellow is 52), Company news 213 slate (low saturation: grey-blue, not blue),
   Tips 262 violet, Billing 180 teal, CBS 142 green, All 242 the brand indigo. The one new colour is the tips violet. */
:root {
    --notif-cat-all: var(--brand-primary);
    --notif-cat-security: var(--warning);
    --notif-cat-billing: var(--updates-teal);
    --notif-cat-store: var(--user-brand-color);            /* always the person's brand, never a fixed red */
    --notif-cat-company: color-mix(in srgb, var(--muted) 80%, var(--ink));   /* a mid slate */
    --notif-cat-tips: #7C3AED;
    --notif-cat-cbs: color-mix(in srgb, var(--success) 70%, var(--muted));   /* CBS green, calmed toward slate like the other families: the solid mark reads heavier than the outline icons */
    --notif-cat-system: var(--muted);
}
.updates-cat-tips     { --updates-bar: var(--notif-cat-tips); }
.updates-cat-security { --updates-bar: var(--notif-cat-security); }
.updates-cat-security.is-urgent { --updates-bar: var(--danger); }   /* B4: red when the deadline is inside 7 days */
.updates-cat-billing  { --updates-bar: var(--notif-cat-billing); }
.updates-cat-store    { --updates-bar: var(--notif-cat-store); }
.updates-cat-system   { --updates-bar: var(--notif-cat-system); }
.updates-cat-company  { --updates-bar: var(--notif-cat-company); }
.updates-cat-cbs      { --updates-bar: var(--notif-cat-cbs); }

/* The tonal circle, shared by the filter buttons and the item icon circles, from --updates-bar (the family; All uses --notif-cat-all):
     rest      14% family over the surface, 1px edge at 40% family, icon 70% family / 30% ink (the pure amber, teal and green fall under
               3:1 on their own tint, 2.5 / 3.0 / 2.4; at 70/30 the icon is 4.1:1 or more on the 14% tint and 3.8:1 or more on 22%)
     hover     22% tint (focus-visible has the ring)
     selected  the solid family fill (90% family / 10% ink, so white on it is 3.2:1 or more) with a white icon, plus the focus ring
   My stores takes its colour from --user-brand-color; its circle is a tint of --user-brand-color-muted and the brand symbol (a mask) in
   the brand colour. */
.updates-item-icon,
.updates-chip {
    --notif-fill: color-mix(in srgb, var(--updates-bar, var(--notif-cat-all)) 14%, var(--surface));
    --notif-fill-hover: color-mix(in srgb, var(--updates-bar, var(--notif-cat-all)) 22%, var(--surface));
    --notif-edge: color-mix(in srgb, var(--updates-bar, var(--notif-cat-all)) 40%, transparent);
    --notif-icon: color-mix(in srgb, var(--updates-bar, var(--notif-cat-all)) 70%, var(--ink));
    --notif-solid: color-mix(in srgb, var(--updates-bar, var(--notif-cat-all)) 90%, var(--ink));
    --notif-on-solid: var(--brand-on-primary);
}
/* Dark: mixing toward --ink would LIGHTEN the solid fill and white text on it would fail, so the dark theme darkens it toward black.
   :where keeps the specificity of the rule above, so the tenant (store) rules below still win. */
:where(html[data-theme="dark"]) .updates-item-icon,
:where(html[data-theme="dark"]) .updates-chip {
    --notif-solid: color-mix(in srgb, var(--updates-bar, var(--notif-cat-all)) 70%, black);
}
.updates-cat-store .updates-item-icon,
.updates-chip.updates-cat-store {
    --notif-fill: var(--user-brand-color-muted);
    --notif-fill-hover: color-mix(in srgb, var(--user-brand-color) 30%, var(--surface));
    --notif-icon: var(--user-brand-color);
    --notif-solid: var(--user-brand-color);
}
/* a LIGHT brand colour (Precision Tune yellow) must not vanish: the icon keeps the brand hue at no more than 55% lightness, and the
   icon on the solid fill is white or black by the fill's own lightness. Browsers without relative colours keep the plain values. */
@supports (color: oklch(from red l c h)) {
    .updates-cat-store .updates-item-icon,
    .updates-chip.updates-cat-store {
        --notif-icon: oklch(from var(--user-brand-color) min(l, 0.55) c h);
        --notif-on-solid: oklch(from var(--user-brand-color) calc((0.62 - l) * 1000) 0 h);
    }
}

/* ---- the panel ---- */
/* header controls: Open all notifications and Close, 40px targets (44 on touch) at least 16px apart (24 on touch) */
.updates-panel-tools { display: flex; align-items: center; gap: 16px; margin-left: auto; }
.updates-open-all,
.updates-panel .updates-panel-tools .modal-close {
    width: 40px;
    height: 40px;
    margin: 0;
    padding: 0;
    display: grid;
    place-items: center;
    border-radius: 8px;
    text-decoration: none;
}
.updates-open-all .icon { width: 20px; height: 20px; fill: currentColor; }
.updates-open-all:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }
@media (pointer: coarse) {
    .updates-panel-tools { gap: 24px; }
    .updates-open-all,
    .updates-panel .updates-panel-tools .modal-close { width: 44px; height: 44px; }
}
.updates-filter {
    flex: none;
    display: flex;
    align-items: center;
    gap: 8px 16px;
    flex-wrap: wrap;
    padding: 8px 14px;
}
/* The category buttons SPREAD EVENLY across the row (space-between, a small minimum gap that shrinks with the viewport) and never wrap.
   Above 480px "Unread only" sits inline to their right, with room between the last icon and it; at 480px and below it wraps to its
   own line below (the chips take the whole row). */
.updates-chips { flex: 1 1 auto; display: flex; flex-wrap: nowrap; justify-content: space-between; gap: 6px; min-width: 0; }
/* A filter button: an ICON circle for every category but CBS, and a TEXT pill (like All) for All and CBS (the tooltip and aria-label
   carry the name). Colour is the category's own family (the same --updates-bar the item icon circle uses, from .updates-cat-*):
     NOT selected  a WHITE (surface) circle, the 1px edge and the icon in the family colour (70% family / 30% ink: 4.9:1 or more on white)
     hover, focus  the light tint (14% family over the surface, the item icon circle's own fill)
     SELECTED      the light tint with the family icon and a slightly STRONGER edge (80% family, drawn 2px: border plus an inset ring);
                   never a solid fill, never white on solid
   "All" has no family and takes the brand colour. */
.updates-chip {
    --notif-edge-strong: color-mix(in srgb, var(--updates-bar, var(--notif-cat-all)) 80%, var(--ink));
    flex: none;
    min-width: 40px;
    height: 40px;
    padding: 0 12px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    border: 1px solid var(--notif-edge);
    border-radius: 99px;
    background: var(--surface);
    color: var(--notif-icon);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}
.updates-chip.is-icon { width: 40px; padding: 0; }
.updates-chip .icon { width: 18px; height: 18px; fill: currentColor; }
/* THE CBS MARK as a category avatar (UI_STANDARDS 9, PROPOSED): sprite symbol ph-cbs-mark-thin (a square box, the mark 81% of it high), the THIN variant
   (bands 6.5 units, ink coverage matched to the Phosphor outline icons), drawn in currentColor so the circle's own colour logic (--notif-icon at rest,
   the family tint) still applies. Because it is now as light as the outline icons it takes the same 18px box as they do, in the 32px item circle and
   the 40px filter button alike. The filled ph-cbs-mark is for large uses only. One rule for every place. */
.updates-item-icon .icon.updates-cbs-mark,
.updates-chip .icon.updates-cbs-mark { width: 18px; height: 18px; }
.updates-chip .updates-brand { width: 18px; height: 18px; }   /* the brand mask takes currentColor: the brand colour at rest */
.updates-chip:hover,
.updates-chip:focus-visible { background: var(--notif-fill); }
.updates-chip.is-on,
.updates-chip.is-on:hover,
.updates-chip.is-on:focus-visible {
    background: var(--notif-fill);
    border-color: var(--notif-edge-strong);
    box-shadow: inset 0 0 0 1px var(--notif-edge-strong);
    color: var(--notif-icon);
}
.updates-chip:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }
@media (max-width: 480px) {
    .updates-chips { flex-basis: 100%; }
}
@media (max-width: 400px) {
    .updates-chips { gap: 2px; }   /* seven 40px buttons must still fit a 360px panel */
}

.updates-panel .modal-body { padding: 0 14px 8px; gap: 0; }
.updates-group { margin: 0 0 8px; }
.updates-group-title { margin: 8px 0 4px; font-size: 12px; font-weight: 700; color: var(--muted); letter-spacing: .02em; }
.updates-panel-footer { flex-wrap: wrap; }   /* the markup also carries .payment-summary--singleline (the row: flex, centred, space-between, 12px gap) */
/* the footer link and See all are the standard text link (.link, theme.css); only the block layout is ours */
.updates-seeall { display: block; padding: 8px 0 4px; }

/* a notification row: icon, title, text, time; actions on the right. Whitespace between rows, not rules. */
.updates-item {
    display: grid;
    grid-template-columns: 24px 32px minmax(0, 1fr) auto;   /* the unread dot, the icon circle, the words, the actions */
    column-gap: 6px;
    align-items: start;
    margin: 0 0 4px;
    padding: 8px 8px 8px 0;
    border: 0;
    border-radius: 10px;
    background: none;
}
.updates-item.is-unread { background: color-mix(in srgb, var(--updates-bar) 7%, var(--surface)); }
/* THE UNREAD DOT: 8px, at the item's left. A button, so a click toggles read and unread (its name says which). Read items show a faint ring
   only on hover and focus, so the control is still found. */
.updates-readdot {
    width: 24px;
    height: 32px;
    margin: 0;
    padding: 0;
    display: grid;
    place-items: center;
    border: 0;
    background: none;
    cursor: pointer;
}
.updates-readdot::before {
    content: "";
    width: var(--updates-unread-dot, 8px);
    height: var(--updates-unread-dot, 8px);
    box-sizing: border-box;
    border-radius: 50%;
    border: 1.5px solid transparent;
}
.updates-item.is-unread .updates-readdot::before { background: var(--brand-primary); border-color: var(--brand-primary); }
.updates-readdot:hover::before,
.updates-readdot:focus-visible::before { border-color: var(--muted); }
.updates-item.is-unread .updates-readdot:hover::before,
.updates-item.is-unread .updates-readdot:focus-visible::before { border-color: var(--brand-primary); background: color-mix(in srgb, var(--brand-primary) 35%, var(--surface)); }
.updates-readdot:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: -2px; border-radius: 6px; }

.updates-item-icon {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    box-sizing: border-box;
    border: 1px solid var(--notif-edge);
    background: var(--notif-fill);
    color: var(--notif-icon);
}
.updates-item-icon .icon { width: 18px; height: 18px; fill: currentColor; }
.updates-item-icon .updates-brand { width: 18px; height: 18px; }
.updates-dot-gap { width: 24px; height: 32px; }   /* Snoozed and Dismissed rows: the dot column, empty, so they lay out like the active list */
.updates-item-body { min-width: 0; }
.updates-item .updates-title { font-weight: 600; line-height: 1.3; }
.updates-item .updates-text { margin: 2px 0 4px; font-size: 13px; line-height: 1.35; }
.updates-links { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 4px 0; }
.updates-links .btn { height: auto; min-height: 28px; padding: 3px 12px; text-decoration: none; }
.updates-item .updates-meta { margin-top: 2px; font-size: 12px; gap: 8px; flex-wrap: wrap; }
.updates-item .updates-meta .updates-when { flex: none; }
.updates-cat-name::before { content: "\00B7"; margin-right: 8px; }
.updates-item-tools { display: flex; align-items: center; gap: 2px; align-self: start; }
.updates-tool {
    width: 36px;
    height: 36px;
    margin: 0;
    padding: 0;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 8px;
    background: none;
    cursor: pointer;                       /* the colour and hover are the standard .icon-btn (the JS adds the class) */
}
.updates-tool .icon { width: 20px; height: 20px; fill: currentColor; }
@media (max-width: 640px), (pointer: coarse) {
    .updates-tool { width: 44px; height: 44px; }
    .updates-readdot { height: 44px; margin: -6px 0; }
}
@media (max-width: 480px) {
    /* a narrow panel: the actions drop under the text, still on the right */
    .updates-item { grid-template-columns: 24px 32px minmax(0, 1fr); }
    .updates-item-tools { grid-column: 3; justify-content: flex-end; }
}

/* ---- the Notifications page and the settings ---- */
.updates-page-head { flex-wrap: wrap; }   /* with .payment-summary--singleline in the markup, as the panel footer */
.updates-page-tools { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.updates-page .card { padding: var(--updates-card-pad); }
.updates-page .updates-item { grid-template-columns: 24px 36px minmax(0, 1fr) auto; padding: 10px 8px 10px 0; }
/* the page's equivalent of the panel footer: Mark all read and the settings gear stay in view while the list scrolls. The STICKY element
   is this inner wrapper, not the card's header or the card: it reaches out over the card's own padding (negative margin = the card's
   padding, one variable) and carries the same padding and the card's own background, so when the page scrolls the header keeps its
   padding and never shows the list's edge above it; the card keeps its padding and rounded corners. */
.updates-page { --updates-card-pad: 24px; }
@media (max-width: 480px) { .updates-page { --updates-card-pad: 16px; } }
/* THE PAGE'S HORIZONTAL MARGINS live in the scroll container's PADDING, not its margin: the scrollbar is then drawn at the container's
   edge, OUTSIDE the 16px, so it never eats the card's right margin (a margin leaves the scrollbar flush against the card), and
   scrollbar-gutter: stable keeps the card the same width whether or not the list is long enough to scroll. Below 1100px the left nav
   is a floating icon rail (layout.css TL-148, about 34px) drawn over the shell gutter and the old 16px margin, which is 32px: the card
   would touch it, so the left padding is 16px more there. */
.updates-page .page-body.page-scroll {
    margin: 0;
    padding: 0 16px 16px;
    box-sizing: border-box;
    scrollbar-gutter: stable;
}
@media (min-width: 641px) and (max-width: 1100px) {
    .updates-page .page-body.page-scroll { padding-left: 48px; }   /* 16 + 48 = 64px: the rail is about 49px wide */
}
/* THE STICKY HEADER holds the title row (Mark all read and the gear), the description and the FILTER row. It never takes more than
   about 40% of the viewport height: the description goes at short heights and the filter row becomes one line that scrolls sideways. */
.updates-page-sticky {
    position: sticky;
    top: 0;
    z-index: 2;
    max-height: 40vh;
    max-height: 40dvh;
    overflow-y: auto;   /* the hard stop: whatever else happens, the header is never taller than that */
    margin: calc(var(--updates-card-pad) * -1) calc(var(--updates-card-pad) * -1) 0;
    padding: var(--updates-card-pad) var(--updates-card-pad) 4px;
    background: var(--surface);
    border-radius: var(--radius) var(--radius) 0 0;
}
.updates-page-sticky .card-header { margin-bottom: 0; }
.updates-page-sticky .updates-filter { padding: 12px 0 4px; }
@media (max-width: 480px) {
    /* a phone: the description goes (the header would otherwise be a third of the screen), and the seven buttons wrap to a second row
       instead of running out of the card (its inner width at 390 is under 300px) */
    .updates-page-sticky .updates-chips { flex-wrap: wrap; justify-content: flex-start; gap: 6px; }
}
@media (max-height: 560px) {
    .updates-page-sticky .updates-chips { flex-wrap: nowrap; }
    .updates-page { --updates-card-pad: 16px; }
    .updates-page-sticky .updates-filter { flex-wrap: nowrap; overflow-x: auto; padding-top: 8px; }
    .updates-page-sticky .updates-chips { flex: 0 0 auto; }
}
.updates-page .card-content { padding-top: 8px; }
.updates-page-note { margin: 12px 0 0; }
.updates-more-row { display: flex; justify-content: center; padding: 12px 0 0; }
/* .btn is display: inline-flex (theme.css), which beats the browser's [hidden] rule: without this the Show more button stayed on screen
   with no next page, and every click asked the server for the first page again and appended it. */
.updates-more-row .btn[hidden] { display: none; }
/* Snoozed and Dismissed are EXPANDABLE sections: the standard <details> (UI_STANDARDS section 5), a caret from the chevron token that
   turns when open, the summary quiet (muted, 0.7) until hover or focus or open (exactly the "Lost your device?" recipe in passkeys.css:
   the opacity is on the summary, so label and caret come alive together) and a count in a small circle badge. */
.updates-fold { margin: 16px 0 0; scroll-margin-top: 88px; }
.updates-fold > summary {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    cursor: pointer;
    list-style: none;
    font-weight: 600;
    color: var(--muted);
    opacity: .7;
    transition: opacity 120ms ease, color 120ms ease;
}
.updates-fold > summary:hover,
.updates-fold > summary:focus-visible,
.updates-fold[open] > summary { opacity: 1; color: var(--ink); }
.updates-fold > summary::-webkit-details-marker { display: none; }
.updates-fold > summary::before {
    content: "";
    flex: none;
    width: 16px;
    height: 16px;
    background-color: currentColor;
    -webkit-mask: var(--chevron-right) center / contain no-repeat;
    mask: var(--chevron-right) center / contain no-repeat;
    transition: transform 120ms ease;
}
.updates-fold[open] > summary::before { transform: rotate(90deg); }
.updates-fold > summary:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; border-radius: 6px; }
/* the count: a small circle badge with the number only (a pill when it has two digits) */
.updates-fold-count {
    display: inline-grid;
    place-items: center;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    box-sizing: border-box;
    border-radius: 99px;
    background: var(--surface-subtle);
    border: 1px solid var(--border);
    color: var(--ink);
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}
.updates-fold-count:empty { display: none; }

.updates-switches { display: flex; flex-direction: column; gap: 4px; }
.updates-switchrow {
    display: grid;
    grid-template-columns: 36px minmax(0, 1fr) auto;
    column-gap: 12px;
    align-items: center;
    padding: 10px 0;
}
.updates-switch-end { display: flex; align-items: center; gap: 8px; }
.updates-switch-end .pref-status { min-height: 0; min-width: 3.5em; text-align: right; }
.updates-switch-label { font-weight: 600; }
/* the switch itself is the standard .switch + .slider (controls.css, the AutoPay "AutoPay is on" switch): brand colour on, neutral grey
   off, nothing overridden. Ours: the standard hides the input with display:none (so the keyboard cannot reach it); here it is kept
   but invisible over the track, and the track gets a focus ring. */
.updates-switch { margin-right: 0; }
.updates-switch input { display: block; position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.updates-switch input:focus-visible + .slider { outline: 2px solid var(--brand-primary); outline-offset: 2px; }
.updates-switch.is-locked input { cursor: not-allowed; }
.updates-settings-actions { flex-wrap: wrap; margin-top: 16px; }   /* with .payment-summary--singleline in the markup */
.updates-settings-snooze { display: flex; flex-direction: column; gap: 4px; max-width: 320px; margin-top: 20px; }
.updates-settings-snooze-line { display: flex; align-items: center; gap: 12px; }
.updates-settings-snooze-line .input { flex: 0 1 200px; min-width: 0; }

/* short viewports (a phone on its side, 640x360 or 844x390): the filter row stays ONE line and scrolls sideways, so the list keeps room */
@media (max-height: 480px) {
    .updates-filter { flex-wrap: nowrap; overflow-x: auto; padding-top: 4px; padding-bottom: 4px; }
    .updates-chips { flex-basis: auto; }
}

/* Notifications page: the settings control is ONLY the gear, the standard .icon-btn (colour and hover are its own), no chrome */
.updates-settings-link {
    width: 40px;
    height: 40px;
    display: grid;
    place-items: center;
    border-radius: 8px;
    text-decoration: none;
}
.updates-settings-link .icon { width: 22px; height: 22px; fill: currentColor; }
.updates-settings-link:focus-visible { outline: 2px solid var(--brand-primary); outline-offset: 2px; }

/* ---------------------------------------------------------------------------------------------
   THE TOAST (docs/design/NOTIFICATIONS_TOAST_SPEC.md): ONE immediate notification, anchored under the bell, right-aligned to it. The card
   is the same .updates-item the panel draws (icon circle, category colour, title, text, action button; updates.js uses the one builder),
   with a close X in place of the actions. It stays until closed or acted; it never takes focus. role="status" aria-live="polite" is on
   the host (Controls/ucUpdatesBell.ascx), which is always in the page, so a screen reader announces what is put into it.
   At 480px and below it is the full width under the header, as the panel is.
   --------------------------------------------------------------------------------------------- */
.updates-toast {
    position: absolute;
    right: calc((var(--updates-bell-size) + 8px) * -1);   /* its right edge is the bell's, as the panel's is */
    top: calc(100% + 8px);
    z-index: 2090;                                         /* under the panel (2100), inside the appbar's stacking context */
    width: min(380px, calc(100vw - 32px));
    color: var(--ink);                                     /* the host sits inside the app bar, whose text is white */
    text-align: left;
    font-size: 14px;
}
    .updates-toast[hidden] { display: none; }

.updates-toast .updates-item {
    grid-template-columns: 32px minmax(0, 1fr) auto;      /* no dot column: a toast is not a list row */
    margin: 0;
    padding: 12px;
    border: 1px solid color-mix(in srgb, var(--updates-bar) 30%, var(--border));
    background: color-mix(in srgb, var(--updates-bar) 7%, var(--surface));
    box-shadow: var(--shadow-3);
    animation: updates-toast-in var(--updates-toast-duration) var(--updates-ease-toast) both;
}
.updates-toast .updates-item-tools { grid-column: auto; justify-content: flex-start; }

@keyframes updates-toast-in {
    from { opacity: 0; transform: translateY(var(--updates-toast-shift)); }
    to   { opacity: 1; transform: none; }
}
@keyframes updates-toast-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@media (max-width: 480px) {
    .updates-toast {
        position: fixed;
        left: 16px;
        right: 16px;
        top: calc(var(--appbar-height, 86px) + 6px);
        width: auto;
    }
}

/* REDUCED MOTION: no slide and no bell ring. The toast simply fades in; the count badge carries the unread state. */
@media (prefers-reduced-motion: reduce) {
    .updates-toast .updates-item { animation-name: updates-toast-fade; }
    .updates-bell.is-pulse .updates-ripple,
    .updates-bell.is-pulse .updates-bell-icon { animation: none; }
    .updates-bell.is-pulse .updates-count { animation: updates-badge-pulse var(--updates-badge-pulse-duration) ease-in-out 1; }   /* no motion: colour and outline only */
}

/* the badge waits for the page to settle (updates.js) so it appears with the ring */
.updates-count { transition: opacity .2s ease; }
.updates-bell.is-held .updates-count { opacity: 0; visibility: hidden; }
@media (prefers-reduced-motion: reduce) { .updates-count { transition: none; } }

/* THE FILTER ROW IS ONE LINE (designer 2026-10-15). "All", "Unread" and the category circles share one row; when they do not fit (a phone) the row
   scrolls sideways with the scrollbar hidden, and the last circle is cut by the edge as the hint. Nothing wraps, the checkbox line is gone. */
.updates-filter { flex-wrap: nowrap; }
.updates-chips { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; justify-content: flex-start; }
.updates-chips::-webkit-scrollbar { display: none; }
@media (min-width: 481px) { .updates-chips { justify-content: space-between; } }
@media (max-width: 480px) { .updates-chips { flex-basis: auto; width: 100%; gap: 6px; } }
/* Unread is a circle like the categories and its glyph IS the unread dot of an item: same size token, same colour, so the dot means one thing everywhere. */
.updates-chip-unread::before { content: ""; width: var(--updates-unread-dot, 8px); height: var(--updates-unread-dot, 8px); border-radius: 50%; background: var(--brand-primary); }
