/* ==========================================================================
   offline-pill.css — the "you are offline" chip, for every page that has one.

   WHY IT LOOKS AND SITS LIKE THIS
   The chip used to be a child of each page's header bar (the client survey's
   .topbar, the office portal's .topbar). On a phone the header is already full
   — hamburger, logo, two lines of title, notification bell, fullscreen button —
   so the chip was squeezed on top of the portal name and came out half-hidden
   behind it: the one message that has to be unmissable was the least readable
   thing on the screen.

   So it is no longer IN the header. It is a fixed, centred chip floating just
   BELOW the header, out of the layout entirely: it cannot be crowded by
   anything, it cannot push anything down, and it stays put while the page
   scrolls. Solid amber with white text (the old pale-amber-on-white fill
   disappeared against a light page and washed out on a dark one), a real
   shadow so it lifts off whatever is behind it, and a pulsing dot.

   HOW A PAGE POSITIONS IT
   Set --offline-pill-top to the height of that page's header (plus a little
   breathing room). The default suits a page with no fixed header at all:

       .topbar-page { --offline-pill-top: calc(var(--topbar-h) + 10px); }

   A page whose header height is only known at runtime (index.ejs measures its
   own) sets the same property from script.

   VISIBILITY
   One mechanism only: the chip is display:none until `.is-visible` is added by
   that page's navigator.onLine listener. Never inline styles, never d-none.
   ========================================================================== */

.offline-pill {
    position: fixed;
    /* The default is carried in the var() fallback, NOT as a :root rule. A
       :root default here loses to nothing but wins over a page's own :root
       rule whenever this file happens to be linked last — which is exactly how
       the office portal's chip ended up back on top of its header. As a
       fallback it only ever applies when the page sets nothing at all. */
    top: calc(var(--offline-pill-top, 0.75rem) + env(safe-area-inset-top, 0px));
    /* Centred on the CONTENT, not always on the viewport. A page whose desktop
       layout is pushed right by a permanent sidebar (the office portal and the
       client survey both are, by --office-sidebar-w / --sidebar-w) sets
       --offline-pill-shift to half that width, and the chip sits over the
       middle of the column the reader is actually looking at instead of half a
       sidebar to the left of it. Every other page leaves it at 0. */
    left: calc(50% + var(--offline-pill-shift, 0px));
    transform: translateX(-50%);
    /* Above sticky headers (1020), BELOW the mobile drawer and its backdrop
       (1035/1040) and below modals and the offline overlay. It was 1090, which
       floated the chip on top of an open navigation drawer — the one thing
       already covering the whole screen and demanding an answer. It must never
       cover something the user has to act on. */
    z-index: 1030;

    display: none;
    align-items: center;
    gap: 0.45rem;
    max-width: min(92vw, 30rem);
    padding: 0.45rem 0.95rem;
    border-radius: 50px;

    background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
    color: #ffffff;
    border: 1px solid rgba(255, 255, 255, 0.35);
    box-shadow: 0 10px 24px -8px rgba(146, 64, 14, 0.75), 0 2px 6px rgba(0, 0, 0, 0.25);
    text-shadow: 0 1px 2px rgba(120, 53, 15, 0.35);

    font-size: 0.78rem;
    font-weight: 700;
    line-height: 1.3;
    /* NOT nowrap. "Viewing Offline" is short enough to stay on one line on its
       own, but the landing page reuses this chip for the much longer
       server-unavailable sentence, and nowrap clipped the end of it off the
       screen on a phone — the half of the message that says what to do. */
    text-align: center;
}

.offline-pill.is-visible {
    display: inline-flex;
    animation: offlinePillIn 0.28s cubic-bezier(0.4, 0, 0.2, 1) both;
}

/* TWO chips up at once. The landing page carries two of these — "you are
   offline" and the red/amber server-reachability notice — and they are both
   fixed at the same top, so whenever both were showing they printed exactly on
   top of each other and neither could be read. They are rare together (a drop
   mid-health-check), but "rare" is not "never", so the second one steps down
   instead of overlapping. Written as a general sibling rule so it holds for any
   page that ever shows two, in source order, with no extra class to set.

   The step is a fixed 2.85rem because a fixed element cannot be offset by the
   measured height of another one in CSS alone. That clears a ONE-LINE chip
   (~30px) comfortably, which is what the first chip always is here — the short
   "Viewing Offline" label. The long, wrapping message is always the second one,
   and nothing is stacked under it. If a page ever puts a wrapping chip FIRST,
   this needs a flex container instead of an offset. */
.offline-pill.is-visible ~ .offline-pill.is-visible {
    top: calc(var(--offline-pill-top, 0.75rem) + 2.85rem + env(safe-area-inset-top, 0px));
}
.offline-pill.is-visible ~ .offline-pill.is-visible ~ .offline-pill.is-visible {
    top: calc(var(--offline-pill-top, 0.75rem) + 5.7rem + env(safe-area-inset-top, 0px));
}

/* The centring translate has to be carried through every keyframe, or the chip
   snaps to the right of centre for the length of the animation. */
@keyframes offlinePillIn {
    0%   { opacity: 0; transform: translateX(-50%) translateY(-10px); }
    100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.offline-pill .dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #ffffff;
    flex: none;
    animation: offlinePulse 1.6s ease-in-out infinite;
}

@keyframes offlinePulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

/* A real server-side failure (a response came back, just not an ok one) as
   opposed to "no network" — red is reserved for the server itself answering
   and saying something is wrong. */
.offline-pill.is-error {
    background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%);
    box-shadow: 0 10px 24px -8px rgba(153, 27, 27, 0.75), 0 2px 6px rgba(0, 0, 0, 0.25);
    text-shadow: 0 1px 2px rgba(127, 29, 29, 0.35);
}

/* Used by the server-unavailable variant on the landing page. */
.offline-pill .notice-retry-btn {
    flex: 0 0 auto;
    margin-left: 0.15rem;
    background: rgba(255, 255, 255, 0.9);
    border: none;
    color: #b45309;
    border-radius: 50px;
    font-size: 0.7rem;
    font-weight: 800;
    padding: 0.2rem 0.65rem;
    min-height: 26px;
    text-shadow: none;
}

.offline-pill.is-error .notice-retry-btn { color: #b91c1c; }
.offline-pill .notice-retry-btn:hover { background: #ffffff; }

/* Phones: the chip has the full width of the screen to itself now that it is
   out of the header, so the text is NOT abbreviated here — being readable is
   the whole point. Only the box tightens. */
@media (max-width: 480px) {
    .offline-pill {
        font-size: 0.75rem;
        padding: 0.4rem 0.85rem;
        max-width: calc(100vw - 24px);
    }
}

@media (prefers-reduced-motion: reduce) {
    .offline-pill.is-visible { animation: none; }
    .offline-pill .dot { animation: none; }
}

/* --------------------------------------------------------------------------
   NOTICE VARIANT — the long-message chip (the landing page's server /
   slow-connection notice), as opposed to the two-word "Viewing Offline" chip
   this file was originally written for.

   WHAT WAS WRONG
   Both used the same rule, and that rule was built for two words: a 50px pill
   radius, shrink-to-fit inline-flex sizing, centred text, and the Retry button
   as an ordinary flex sibling. Give that a full sentence and it collapses —
   the text box is squeezed by the button next to it, so a one-line message
   wrapped into six cramped lines inside a giant amber oval with the button
   floating in the middle of it. Unreadable, and it looked broken.

   THE FIX
   A long message is a banner, not a pill. This variant takes a real width
   instead of shrinking to fit, lays out as icon | message | action with the
   message allowed to take the slack (min-width: 0 is what actually lets a
   flex item wrap instead of being squeezed), left-aligns the text so the
   ragged edge is on one side only, and drops to a 14px radius because a 50px
   radius on a two-line box is an oval, not a rounded rectangle.
   -------------------------------------------------------------------------- */

.offline-pill.is-notice {
    display: none; /* the .is-visible rule below re-enables it, same as the base chip */
    width: min(94vw, 33rem);
    max-width: min(94vw, 33rem);
    border-radius: 14px;
    padding: 0.6rem 0.65rem 0.6rem 0.85rem;
    gap: 0.6rem;
    text-align: left;
    justify-content: flex-start;
    font-weight: 600;
    line-height: 1.35;
}

.offline-pill.is-notice.is-visible { display: flex; }

/* The icon must never be the thing that gives up space. */
.offline-pill.is-notice > i {
    flex: 0 0 auto;
    font-size: 1rem;
    line-height: 1.35;
}

/* min-width: 0 is the whole fix for the six-line wrap: without it a flex item
   refuses to shrink below its longest word's line box and the browser squeezes
   the column instead of wrapping the text. */
.offline-pill.is-notice .label {
    flex: 1 1 auto;
    min-width: 0;
    text-align: left;
}

.offline-pill.is-notice .notice-retry-btn {
    flex: 0 0 auto;
    align-self: center;
    margin-left: 0;
}

@media (max-width: 480px) {
    .offline-pill.is-notice {
        width: calc(100vw - 20px);
        max-width: calc(100vw - 20px);
        font-size: 0.76rem;
        padding: 0.55rem 0.6rem 0.55rem 0.75rem;
    }
}

/* The stacking offset assumes a one-line first chip; the notice is the taller
   one and is always second, so it is the one that steps down. Two lines of
   0.76rem plus padding needs a little more clearance than the 2.85rem the base
   rule gives a one-line pill. */
.offline-pill.is-visible ~ .offline-pill.is-notice.is-visible {
    top: calc(var(--offline-pill-top, 0.75rem) + 3.1rem + env(safe-area-inset-top, 0px));
}

/* ==========================================================================
   WEAK-CONNECTION PROMPT — the "your connection can't get you through the
   survey; take it offline instead?" dialog raised by /js/connection-prompt.js
   on the landing page, /login and /client-login.

   WHY IT EXISTS
   navigator.onLine is a two-state signal about the network INTERFACE and it
   is wrong in both directions for this question: it reports "online" for a
   phone holding one unusable bar of data, and on Android it reports "offline"
   for a connection with full signal but no working route. Either way the
   visitor is stuck on a page that looks fine and cannot reach the survey. The
   module's job is to notice; this file's job is to make the question look
   like it belongs to the page asking it.

   TWO SKINS, DECLARED BY THE PAGE — NOT GUESSED
   The first version of this was one white card everywhere. That is right on
   /login and /client-login, which render forceLightMode and are white cards
   with navy buttons — and plainly foreign on the landing page, which is
   permanently navy. So each page names its skin (.wc-theme-light /
   .wc-theme-dark) and the dialog adopts that page's own palette: navy
   (#251d3a) and gold (#ffcc00), the two colours the rest of this project is
   built from, rather than a third accent of its own.

   Light: white card, navy primary button — the same .btn-login those two
   pages already use, so the dialog's main action looks like their main
   action. Dark: navy card, GOLD primary — on the landing page navy-on-navy
   would disappear, and gold is exactly what that page's own Login button
   uses for the same reason.

   Structurally it still matches .offline-transition-card in client-survey.ejs
   (same size, radius, icon disc and stacked full-width buttons) so the
   project's connectivity dialogs read as one system.
   ========================================================================== */

.weak-connection-overlay {
    position: fixed;
    inset: 0;
    /* Above .offline-pill (1030) and any page header — this is a question the
       visitor has to answer, so nothing may sit on top of it. */
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.25rem;
    padding-top: calc(1.25rem + env(safe-area-inset-top, 0px));
    padding-bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
    background: rgba(15, 23, 42, 0.72);
    backdrop-filter: blur(3px);
}

/* Same single-mechanism rule as .offline-pill above: the [hidden] attribute
   is the only thing that controls this, never an inline style or d-none. */
.weak-connection-overlay[hidden] { display: none; }

/* A navy page needs a deeper scrim than a white one, or the card floats on a
   grey wash that reads as a rendering glitch rather than a dimmed page. */
.weak-connection-overlay.wc-theme-dark { background: rgba(8, 5, 18, 0.8); }

.weak-connection-card {
    border-radius: 20px;
    padding: 1.85rem 1.6rem;
    max-width: 380px;
    width: 100%;
    text-align: center;
    animation: wcCardIn 0.26s cubic-bezier(0.34, 1.26, 0.64, 1) both;
}

@keyframes wcCardIn {
    0%   { opacity: 0; transform: translateY(14px) scale(0.97); }
    100% { opacity: 1; transform: translateY(0) scale(1); }
}

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

.weak-connection-icon {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    margin: 0 auto 0.9rem;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
}

.weak-connection-card h4 {
    font-weight: 700;
    font-size: 1.15rem;
    margin: 0 0 0.5rem;
    letter-spacing: -0.01em;
}

.weak-connection-card p {
    font-size: 0.88rem;
    line-height: 1.55;
    margin: 0 0 1.35rem;
}

.weak-connection-actions {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}

/* Deliberately NOT .btn — the landing page and the two login pages load
   different button systems (and the landing page's .btn is its own marketing
   button), so this dialog carries its own two buttons and is identical on all
   three. */
.weak-connection-actions button,
.weak-connection-actions a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    min-height: 46px;
    padding: 0.6rem 1rem;
    border-radius: 12px;
    border: 1px solid transparent;
    font-size: 0.92rem;
    font-weight: 700;
    line-height: 1.2;
    text-decoration: none;
    cursor: pointer;
    transition: filter 0.18s ease, background 0.18s ease, transform 0.18s ease;
}

.weak-connection-actions .wc-btn-primary:active,
.weak-connection-actions .wc-btn-secondary:active { transform: translateY(1px); }

/* ---------------- LIGHT SKIN — /login, /client-login ---------------- */

.wc-theme-light .weak-connection-card {
    background: #ffffff;
    color: #1e1b33;
    box-shadow: 0 30px 60px -15px rgba(15, 23, 42, 0.5);
}

.wc-theme-light .weak-connection-icon {
    background: rgba(37, 29, 58, 0.08);
    color: #251d3a;
}

.wc-theme-light .weak-connection-card h4 { color: #1e1b33; }
.wc-theme-light .weak-connection-card p { color: #4a5568; }

/* Mirrors .btn-login in /css/auth.css — same gradient, weight and shadow — so
   on those two pages the dialog's main action and the page's main action are
   visibly the same button. */
.wc-theme-light .wc-btn-primary {
    background: linear-gradient(135deg, #251d3a, #2a2550);
    color: #ffffff;
    box-shadow: 0 8px 18px -6px rgba(37, 29, 58, 0.55);
}
.wc-theme-light .wc-btn-primary:hover { filter: brightness(1.12); color: #ffffff; }

.wc-theme-light .wc-btn-secondary {
    background: #ffffff;
    color: #3d4759;
    border-color: #d7dbe3;
}
.wc-theme-light .wc-btn-secondary:hover { background: #f4f6fa; color: #3d4759; }

/* ---------------- DARK SKIN — the landing page ---------------- */

.wc-theme-dark .weak-connection-card {
    /* A step lighter than the page's own --navy-950 (#251d3a) so the card
       lifts off it, with a hairline edge doing the work a shadow cannot do
       against a dark ground. */
    background: linear-gradient(160deg, #2c2348 0%, #241c39 100%);
    color: #f2f0f8;
    border: 1px solid rgba(255, 255, 255, 0.1);
    box-shadow: 0 30px 60px -15px rgba(0, 0, 0, 0.75);
}

.wc-theme-dark .weak-connection-icon {
    background: rgba(255, 204, 0, 0.14);
    color: #ffcc00;
}

.wc-theme-dark .weak-connection-card h4 { color: #ffffff; }
.wc-theme-dark .weak-connection-card p { color: #c5bfd8; }

/* Gold, matching that page's own .btn-admin Login button. Navy-on-navy would
   vanish here, and gold is the landing page's established "this is the
   action" colour. Navy text on gold clears AA comfortably. */
.wc-theme-dark .wc-btn-primary {
    background: linear-gradient(135deg, #ffd84d, #ffcc00);
    color: #251d3a;
    box-shadow: 0 8px 20px -6px rgba(255, 204, 0, 0.45);
}
.wc-theme-dark .wc-btn-primary:hover { filter: brightness(1.06); color: #251d3a; }

.wc-theme-dark .wc-btn-secondary {
    background: rgba(255, 255, 255, 0.04);
    color: #ded9ec;
    border-color: rgba(255, 255, 255, 0.2);
}
.wc-theme-dark .wc-btn-secondary:hover { background: rgba(255, 255, 255, 0.1); color: #ffffff; }

@media (max-width: 480px) {
    .weak-connection-card { padding: 1.45rem 1.15rem; border-radius: 16px; }
    .weak-connection-icon { width: 48px; height: 48px; font-size: 1.25rem; margin-bottom: 0.75rem; }
    .weak-connection-card h4 { font-size: 1.05rem; }
    .weak-connection-card p { font-size: 0.85rem; margin-bottom: 1.15rem; }
    .weak-connection-actions button,
    .weak-connection-actions a { min-height: 44px; font-size: 0.88rem; }
}
