/* ========================================================
   NOTIFICATION PANEL
   ========================================================
   A toast is gone in four seconds. That is the right lifetime for a
   confirmation and the wrong one for everything else: an operator who looks
   up from a docket half a second late has no way to find out what the app
   just told them, and a low-stock warning raised on the dashboard is
   invisible from every other screen.

   This is the record of those messages — every toast the session has
   raised, plus the system alerts and version updates that never got one —
   reachable from the bell in the mobile header and from the sidebar on
   desktop.

   WHERE THE DATA LIVES: entirely in the browser (localStorage), written by
   static/js/main.js. There is no notifications table and no new route. On
   the PythonAnywhere free tier that is not a shortcut, it is the design:
   the single web worker loses all in-memory state on every reload, and a
   database-backed notification feed would add a table, a migration, a poll
   endpoint and a query to every page render in exchange for something the
   client already knows.

   Structure, so the media queries below make sense:
     .notif-bell     the trigger. Rendered twice (mobile header + sidebar),
                     class-based rather than id-based for exactly that reason.
     .notif-overlay  the scrim behind the panel.
     .notif-panel    the panel itself — a right-hand drawer on desktop, a
                     full-height sheet on a phone.
   ======================================================== */


/* ---- Trigger ------------------------------------------------------- */

.notif-bell {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 44px;
    min-height: 44px;
    padding: 0;
    background: none;
    border: none;
    color: inherit;
    font-family: inherit;
    font-size: inherit;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.notif-bell:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* Inside the sidebar the trigger is one nav item among several, so it has
   to match them rather than the header's bare icon button. */
.nav-links .notif-bell {
    width: 100%;
    justify-content: flex-start;
    padding: 10px 16px;
    border-radius: var(--radius-md);
    color: rgba(255, 255, 255, 0.7);
    font-weight: 500;
    transition: all 0.2s;
}
.nav-links .notif-bell:hover { background: rgba(255, 255, 255, 0.08); color: var(--sidebar-text, #fff); }
.nav-links .notif-bell:focus-visible { outline: 2px solid #fff; outline-offset: -2px; }
.nav-links .notif-bell .icon { width: 20px; flex-shrink: 0; }

/* The unread count. Absolutely positioned so it never changes the size of
   the button underneath it — a badge that appears and disappears must not
   shift the rest of the header sideways. */
.notif-badge {
    position: absolute;
    top: 2px;
    left: 22px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--radius-full);
    background: var(--negative);
    color: var(--on-negative);
    font-size: 10px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
}
/* In the sidebar the item is full width, so the badge belongs at the end of
   the row rather than stuck to the icon. */
.nav-links .notif-bell .notif-badge {
    position: static;
    margin-left: auto;
}


/* ---- Overlay ------------------------------------------------------- */

.notif-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--z-notif-overlay, 1060);
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    transition: opacity var(--dur-base, 200ms) var(--ease-out, ease);
}
.notif-overlay.active { opacity: 1; }


/* ---- Panel --------------------------------------------------------- */

.notif-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: var(--z-notif-panel, 1070);
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    width: min(380px, 100vw);
    background: var(--surface);
    border-left: 1px solid var(--border);
    box-shadow: var(--elev-3);
    /* Off-screen by default and translated in, rather than toggled with
       display: the panel keeps its scroll position and the transition has
       something to animate. */
    transform: translateX(100%);
    transition: transform var(--dur-slow, 320ms) var(--ease-in-out, ease);
    will-change: transform;
}
.notif-panel.active { transform: translateX(0); }

/* `hidden` on the panel/overlay is what keeps them out of the tab order and
   the accessibility tree while closed. It has to be beaten explicitly,
   because [hidden] is display:none and a transform cannot animate from it —
   main.js removes the attribute one frame BEFORE adding .active. */
.notif-panel[hidden],
.notif-overlay[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
    .notif-panel, .notif-overlay { transition: none; }
}

.notif-header {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-shrink: 0;
    padding: var(--space-3);
    border-bottom: 1px solid var(--border);
    /* Clears the notch in the installed app, where the panel is full height
       and there is no browser chrome above it. */
    padding-top: calc(var(--space-3) + env(safe-area-inset-top, 0px));
}
.notif-header h2 {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: 16px;
    font-weight: 700;
    color: var(--text-main);
}
.notif-header .icon { color: var(--brand); }

.notif-close {
    margin-left: auto;
    min-width: 44px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    cursor: pointer;
}
.notif-close:hover { background: var(--input-bg); color: var(--text-main); }
.notif-close:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }


/* ---- Category filters ---------------------------------------------- */

.notif-filters {
    display: flex;
    gap: var(--space-1);
    flex-shrink: 0;
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.notif-chip {
    flex: 1 0 auto;
    min-height: 34px;
    padding: 0 12px;
    border: 1px solid var(--input-border);
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--text-muted);
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
}
.notif-chip:hover { border-color: var(--brand); color: var(--text-main); }
.notif-chip.active {
    background: var(--brand);
    border-color: var(--brand);
    /* --on-brand, not #fff: on the dark theme --brand is the lighter
       orange and white on it is 2.4:1. */
    color: var(--on-brand);
}
.notif-chip:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }


/* ---- The list ------------------------------------------------------ */

.notif-list {
    flex: 1;
    margin: 0;
    padding: 0;
    list-style: none;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;   /* don't scroll the page behind the panel */
}

.notif-item {
    border-bottom: 1px solid var(--border);
    /* The colour band is how a warning is told from a confirmation at a
       glance; the per-type colours are set on the modifier classes below. */
    border-left: 3px solid transparent;
}
.notif-item.is-unread { background: var(--brand-wash); }

/* The row inside the <li>.
   The padding lives HERE and not on the .notif-item, because a server
   notification renders this element as an <a> covering the whole row — an
   operator tapping anywhere on "Sale · Awaiting approval" should land on
   Records. Padding on the li would leave a dead 12px gutter around a target
   that is already being aimed at with a thumb. */
.notif-row {
    display: flex;
    gap: var(--space-2);
    padding: var(--space-3);
}

/* A linked row. `color: inherit` so an alert is not rendered in link blue —
   the severity band on the left is the colour signal here, and a blue
   message would compete with it. */
.notif-link {
    color: inherit;
    text-decoration: none;
}
.notif-link:hover { background: var(--input-bg); }
/* Inset, because an outline drawn outside the row is clipped by the
   scrolling list and only half of it is visible on the first and last item. */
.notif-link:focus-visible {
    outline: 2px solid var(--brand);
    outline-offset: -2px;
}
/* The affordance that the row goes somewhere. Only on linked rows, so a
   local "Saved" entry does not look tappable when it is not. */
.notif-link .notif-message::after {
    content: ' ›';
    color: var(--brand-ink);
    font-weight: 700;
}

.notif-item .notif-icon { flex-shrink: 0; width: 18px; height: 18px; margin-top: 2px; }
.notif-item.type-success { border-left-color: var(--positive); }
.notif-item.type-success .notif-icon { color: var(--positive-ink); }
.notif-item.type-error   { border-left-color: var(--negative); }
.notif-item.type-error   .notif-icon { color: var(--negative-ink); }
.notif-item.type-warning { border-left-color: var(--caution-ink); }
.notif-item.type-warning .notif-icon { color: var(--caution-ink); }
.notif-item.type-info    { border-left-color: var(--brand); }
.notif-item.type-info    .notif-icon { color: var(--brand-ink); }

.notif-body { flex: 1; min-width: 0; }
.notif-message {
    margin: 0;
    color: var(--text-main);
    font-size: 13px;
    line-height: 1.45;
    /* Species names, batch IDs and destinations arrive here verbatim; one
       long unbroken string must wrap rather than widen the panel. */
    overflow-wrap: anywhere;
}
.notif-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-top: 4px;
    color: var(--text-muted);
    font-size: 11px;
}
/* The category label. Deliberately quiet: it is there for scanning, not
   reading. */
.notif-cat {
    padding: 1px 6px;
    border: 1px solid var(--input-border);
    border-radius: var(--radius-full);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.notif-empty {
    margin: 0;
    padding: var(--space-5) var(--space-3);
    color: var(--text-muted);
    font-size: 13px;
    text-align: center;
}
.notif-empty .icon { display: block; width: 28px; height: 28px; margin: 0 auto var(--space-2); opacity: 0.5; }

/* ---- Device-notification opt-in ------------------------------------- */
/* Sits between the list and the footer. flex-shrink: 0 for the same reason
   the footer has it — the list above is the flexible child and must be the
   one that gives up height when the panel is short, otherwise this block
   collapses and the button becomes unreadable on a small phone in landscape. */
.notif-os {
    flex-shrink: 0;
    padding: var(--space-3);
    border-top: 1px solid var(--border);
    background: var(--input-bg);
}
.notif-os-btn { width: 100%; }
.notif-os-status {
    margin: var(--space-2) 0 0;
    color: var(--text-muted);
    font-size: 11px;
    line-height: 1.5;
}
/* When the button is hidden — unsupported browser, or permission already
   refused — the status line is the whole content of the block and should not
   carry the margin that separated it from a button that is not there. */
.notif-os-btn[hidden] + .notif-os-status { margin-top: 0; }

.notif-footer {
    display: flex;
    gap: var(--space-2);
    flex-shrink: 0;
    padding: var(--space-3);
    border-top: 1px solid var(--border);
    /* Clear of the home indicator in the installed app. */
    padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
}
.notif-footer .btn { flex: 1; }
