﻿/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */

/* Theme tokens. Light values are the defaults; the dark block below overrides
   them when the OS is in dark mode. Shell components reference these so the
   whole chrome (body, top bar, tiles, tab bar) flips together. */
:root {
    --bg: #ffffff;
    --text: #111111;
    --text-muted: #5a5a5a;
    --surface: #ffffff;
    --surface-grad-end: #dcebfc;
    --border: #dddddd;
    --brand: #0871ce;
    --tab-active: #0871ce;
    --tab-inactive: #6b7280;

    /* --- Unified mobile design system (the "Min profil" look). Used by the
       .ds-* components below. Reference these on any card/list/settings page. --- */
    --ds-surface: #F4F7FB;
    --ds-paper: #FFFFFF;
    --ds-ink: #0F1F3D;
    --ds-ink2: #3F4A60;
    --ds-ink3: #8893A8;
    --ds-rule: #DCE4F0;
    --ds-accent: #0871CE;
    --ds-accent-mid: #7BB3E8;
    --ds-accent-tint: #DCEBFC;
    --ds-accent-tint-soft: #EEF5FE;
    --ds-accent-ink: #063256;
    --ds-danger: #C8443B;
    --ds-danger-tint: #FBE7E5;
    --ds-warm: #E07A3A;
    --ds-warm-tint: #FBE9DA;
    --ds-ok: #1F8A5B;
    --ds-cta: #063256;
    --ds-cta-disabled: #C7D2E2;
    --ds-cta-disabled-ink: #7E8AA2;
    --ds-chevron: #BCC6D6;

    /* 67px .fullscreenContent offset + ~56px app bar + ~55px tab bar
       + 12px breathing room. Used by .ds-map--fill. */
    --nx-map-chrome: 190px;

    /* The app's UI font, named so pages stop re-declaring it (and stop
       drifting to fonts no phone has). */
    --nx-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

/* Unified design-system dark palette (applied by the layout's pre-paint script). */
:root[data-theme="dark"] {
    --ds-surface: #121417;
    --ds-paper: #1E2126;
    --ds-ink: #E8EAED;
    --ds-ink2: #B8BDC6;
    --ds-ink3: #9AA0A6;
    --ds-rule: #333A42;
    --ds-accent: #4EA3EF;
    --ds-accent-mid: #5E9BD6;
    --ds-accent-tint: #17314A;
    --ds-accent-tint-soft: #14222F;
    --ds-accent-ink: #9ECBF0;
    --ds-danger: #E0635A;
    --ds-danger-tint: #3A1F1D;
    --ds-warm: #E8884A;
    --ds-warm-tint: #3A2A1C;
    --ds-ok: #2FA871;
    --ds-cta: #0871CE;
    --ds-cta-disabled: #2A2F36;
    --ds-cta-disabled-ink: #6B7280;
    --ds-chevron: #5B6470;
}

/* Dark palette, shared by the manual override and the OS fallback below. */
:root[data-theme="dark"] {
    --bg: #121417;
    --text: #e8eaed;
    --text-muted: #9aa0a6;
    --surface: #1e2126;
    --surface-grad-end: #252b33;
    --border: #333a42;
    --brand: #0871ce;
    --tab-active: #4ea3ef;
    --tab-inactive: #8a9099;
}

/* No-JS fallback: follow the OS, but only until a manual preference is applied
   (the pre-paint script sets data-theme, which disables this rule). */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme]) {
        --bg: #121417;
        --text: #e8eaed;
        --text-muted: #9aa0a6;
        --surface: #1e2126;
        --surface-grad-end: #252b33;
        --border: #333a42;
        --brand: #0871ce;
        --tab-active: #4ea3ef;
        --tab-inactive: #8a9099;
    }
}

a.navbar-brand {
  white-space: normal;
  text-align: center;
  word-break: break-all;
}

/* Provide sufficient contrast against white background */
a {
  color: #0366d6;
}

.btn-primary {
  color: #fff;
  background-color: #1b6ec2;
  border-color: #1861ac;
}

.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
  color: #fff;
  background-color: #1b6ec2;
  border-color: #1861ac;
}

/* Sticky footer styles
-------------------------------------------------- */
html {
  font-size: 14px;
}
@media (min-width: 768px) {
  html {
    font-size: 16px;
  }
}

.border-top {
  border-top: 1px solid #e5e5e5;
}
.border-bottom {
  border-bottom: 1px solid #e5e5e5;
}

.box-shadow {
    /*box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .08);*/
    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}

button.accept-policy {
  font-size: 1rem;
  line-height: inherit;
}

/* Sticky footer styles
-------------------------------------------------- */
html {
  position: relative;
  min-height: 100%;
}

body {
    font-family: var(--nx-font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
    margin: 0px;
    background-color: var(--bg);
    color: var(--text);
    /* Kill the grey tap flash and the legacy 300ms click delay for a native-feeling tap. */
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
    /* Respect the notch / home indicator when running as an installed PWA (viewport-fit=cover). */
    padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

/* Slim top brand bar (replaces the old Bootstrap hamburger navbar). */
.app-topbar {
    position: relative;
    /* Paint the bar — and crucially its .box-shadow — above the page content
       below it. Positioned content that follows in the DOM (.ds-page is
       position: relative) otherwise draws its opaque background straight over
       the shadow, clipping it into a hard-edged rectangle that reads as a box
       seam under the header. Kart escapes this only because
       .fullscreenContent starts 11px down, past the shadow.
       Keep this at 2: the login page's help button sits at z-index 3 to stay
       above the bar, and every overlay (tab bar 9970, modal 9982, sheets) is
       far higher. */
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 56px;
    margin-bottom: 10px;
}
.app-brand img {
    height: 40px;
    border: 0;
}

/* Light / dark / system theme selector, pinned to the right of the top bar. */
.theme-switch {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    gap: 2px;
    background: rgba(255, 255, 255, 0.15);
    border-radius: 999px;
    padding: 2px;
}
.theme-switch button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: transparent;
    color: rgba(255, 255, 255, 0.7);
    cursor: pointer;
}
.theme-switch button svg {
    width: 17px;
    height: 17px;
}
.theme-switch button.active {
    background: #ffffff;
    color: var(--brand);
}

/* Leave room for the fixed bottom tab bar so content never hides behind it. */
main[role="main"] {
    padding-bottom: 76px;
}

/* Fixed bottom tab bar — thumb-reachable primary navigation. */
.bottom-tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.12);
    z-index: 9970;
    /* Sit above the iOS home indicator. */
    padding-bottom: env(safe-area-inset-bottom);
}
.bottom-tabbar .tab {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: 54px;
    padding: 6px 2px 4px;
    font-size: 0.62rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--tab-inactive);
}
.bottom-tabbar .tab svg {
    width: 24px;
    height: 24px;
}
.bottom-tabbar .tab span {
    /* 11px fixed, not rem: the root is 14px on phones, so the previous
       0.62rem rendered at 8.7px — under the iOS (10pt) and Material (12sp)
       floors for tab labels. Fixed px keeps it legible if the root changes. */
    font-size: 11px;
    line-height: 1;
    white-space: nowrap;
}
/* Five tabs, and "Innstillinger" is by far the widest label (~60px at 11px semibold).
   Its slot is 71px at 375px wide and 68px at 360px, but only 60px on a 320px screen --
   there the label fills its tab edge to edge and sits flush against "Profil". Drop a
   point on those screens rather than truncating the word, which would look cheap on
   every phone to rescue one that is all but extinct (iPhone SE 1st gen). */
@media (max-width: 359px) {
    .bottom-tabbar .tab span {
        font-size: 10px;
    }
}

.bottom-tabbar .tab.active {
    color: var(--tab-active);
}
.bottom-tabbar .tab:active {
    transform: scale(0.94);
    transition: transform 0.08s ease;
}

/* Prevent iOS from auto-zooming when focusing form fields (any input < 16px triggers it). */
input, select, textarea {
    font-size: 16px;
}
.footer {
  position: absolute;
  bottom: 0;
  width: 100%;
  white-space: nowrap;
  line-height: 60px; /* Vertically center the text there */
}
.bg-nxblue {
    background-color: #0871ce;
}

.header {
    background: linear-gradient(0deg, rgba(58, 104, 194, 1) 12%, rgba(90, 134, 213, 1) 100%);
    width: 100%;
    text-align: center;
    align-content: center;
    font-size: 1.2rem;
    font-weight: bold;
    border: 1px solid #012152;
    color: #fff;
    margin-bottom: 10px;
    white-space: nowrap;
    box-shadow: 0 4px 8px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
    text-shadow: -1px 1px 0 #012152, 1px 1px 0 #012152, 1px -1px 0 #012152, -1px -1px 0 #012152;
}
.navBackBtn {
    width: 50px;
    background: linear-gradient(#fff, #dcebfc);
    border-right: 0px;
    padding-left: 15px;
    padding-right: 15px;
    cursor: pointer;
}

@media (hover: hover) {
    .navBackBtn:hover {
        border: solid 1px #102f6b;
        background: linear-gradient(0deg, rgba(58, 104, 194, 1) 12%, rgba(90, 134, 213, 1) 100%);
        box-shadow: 0 4px 8px 0 rgb(0 0 0 / 40%), 0 6px 20px 0 rgb(0 0 0 / 40%);
    }
}

/* Dashboard tile. Sized by its grid cell (.tileGrid) rather than by viewport
   units: the old width: 25vw / height: 14vh meant three tiles plus the table's
   cell padding overflowed a 320px phone into horizontal scroll, and 14vh
   collapsed the tiles to a sliver in landscape. Now the cell decides the width
   and aspect-ratio decides the height, so the tile is square at any size. */
.tile {
    text-align: center;
    border: solid 1px var(--border);
    border-radius: 15px;
    font-size: 0.9rem;
    font-weight: bold;
    color: var(--text);
    /* Labels wrap instead of being clipped — "Utforsk Sverige" never fit on
       one line at tile width. */
    white-space: normal;
    line-height: 1.15;
    overflow-wrap: break-word;
    width: 100%;
    /* Fill the grid row rather than forcing a fixed square: the grid stretches
       every cell in a row to the tallest, so tiles stay uniform, and a label
       that wraps to two lines grows the row instead of overflowing a rigid
       aspect-ratio box. min-height is the floor when every label is one line. */
    height: 100%;
    min-height: 96px;
    background: linear-gradient(var(--surface), var(--surface-grad-end));
    padding: 12px 5px 10px;
    /* Positioning + clipping context for the tap ripple and the loading
       indicators below, which are drawn as pseudo-elements on this box. */
    position: relative;
    overflow: hidden;
    transition: transform 0.16s cubic-bezier(0.2, 0.8, 0.3, 1), box-shadow 0.2s ease;
}

@media (hover: hover) {
    .tile:hover {
        color: #fff;
        border: solid 1px #ff0000;
        background: linear-gradient(0deg, rgba(58, 104, 194, 1) 12%, rgba(90, 134, 213, 1) 100%);
        box-shadow: 0 4px 8px 0 rgb(0 0 0 / 40%), 0 6px 20px 0 rgb(0 0 0 / 40%);
    }
}

/* Touch devices: give a quick visual press state instead of a sticky hover.
   Fires from CSS alone, so the tile reacts on contact even before any script
   runs — the first of the three signals a tap gets. */
.tile:active {
    color: #fff;
    border: solid 1px var(--ds-accent);
    background: linear-gradient(0deg, rgba(58, 104, 194, 1) 12%, rgba(90, 134, 213, 1) 100%);
    transform: scale(0.96);
    transition: transform 0.08s ease;
}

/* ── Dashboard tap feedback ─────────────────────────────────────────────────
   Three signals, each answering a different question.
   1. "Did it register?"  — the :active press above, plus a ripple from the
      exact point touched, so the feedback is tied to the finger.
   2. "Which one did I hit?" — the chosen tile lifts and glows while every
      other tile dims back, making the target unambiguous.
   3. "Is something happening?" — a progress bar runs along the tile's bottom
      edge and a spinner turns, for the seconds before the next page paints.
   All of it is decoration on top of a normal link: if the script never runs,
   the navigation still works exactly as before. */

/* 1. Ripple, positioned at the touch point by site.js. */
.nx-ripple {
    position: absolute;
    width: 12px;
    height: 12px;
    margin: -6px 0 0 -6px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255, 255, 255, 0.75), rgba(255, 255, 255, 0));
    transform: scale(1);
    opacity: 0.9;
    pointer-events: none;
    animation: nx-ripple 0.55s cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}
@keyframes nx-ripple {
    to { transform: scale(22); opacity: 0; }
}

/* 2. The chosen tile comes forward; its neighbours recede. */
.tileGrid.is-navigating .tileCell {
    transition: opacity 0.25s ease, filter 0.25s ease;
    opacity: 0.35;
    filter: saturate(0.55);
}
.tileGrid.is-navigating .tileCell.is-going {
    opacity: 1;
    filter: none;
    /* Paint above its neighbours while scaled up — grid items otherwise stack
       in DOM order and the tiles after it would clip the lift. */
    position: relative;
    z-index: 1;
}
.tileCell.is-going .tile {
    transform: scale(1.05);
    box-shadow: 0 12px 30px rgba(8, 113, 206, 0.38);
    border-color: var(--ds-accent);
    /* Hold the pressed colours after the finger lifts: the tile stays
       "activated" until the next page paints, and the white spinner and
       progress bar below need a dark ground to read against. */
    background: linear-gradient(0deg, rgba(58, 104, 194, 1) 12%, rgba(90, 134, 213, 1) 100%);
    color: #ffffff;
}

/* 3. Loading indicators on the chosen tile. */
.tile--loading::before {
    content: "";
    position: absolute;
    left: 0;
    bottom: 0;
    height: 3px;
    width: 100%;
    background: rgba(255, 255, 255, 0.92);
    transform-origin: left center;
    animation: nx-tile-progress 2.4s cubic-bezier(0.15, 0.85, 0.25, 1) forwards;
}
@keyframes nx-tile-progress {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
}
.tile--loading::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 26px;
    height: 26px;
    margin: -13px 0 0 -13px;
    border: 3px solid rgba(255, 255, 255, 0.35);
    border-top-color: #ffffff;
    border-radius: 50%;
    animation: nx-spin 0.7s linear infinite;
}

.overlay {
    background: rgba(0, 0, 0, 0.3);
    backdrop-filter: blur(3px);
    width: 100%;
    height: 100%;
    min-height: 100%;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 9980;
    text-align: center;
    vertical-align: middle;
    display: none;
}

/* Primary action button on the pages that predate .ds-cta ("Opprett nytt sted",
   the BuddyMap info-window actions, the modal close). It was the last button
   still drawn as a blue gradient behind a hard #304175 outline, which read as a
   different material from every other control. Same fill, radius and press
   feedback as .ds-cta--active now; call sites override width/height inline, so
   only the look changes. */
.saveBtn {
    width: 220px;
    height: 50px;
    text-align: center;
    border: none;
    border-radius: 14px;
    font-size: 1.2rem;
    font-weight: bold;
    color: #fff;
    white-space: nowrap;
    background: var(--ds-cta);
    cursor: pointer;
    padding-top: 9px;
    transition: transform 0.08s ease, box-shadow 0.2s ease;
}

.saveBtn:active {
    transform: scale(0.97);
}

/* The remote-control image button on both alarm panels — Panel and PanelNX20
   declared the identical rule. */
.remotecontrolBtn {
    height: 135px;
}

    @media (hover: hover) {
        .saveBtn:hover {
            box-shadow: 0 6px 18px rgba(15, 31, 61, 0.28);
        }
    }

.savedBtn {
    width: 230px;
    height: 50px;
    text-align: center;
    border: solid 1px #1e4e40;
    border-radius: 12px;
    font-size: 1.2rem;
    font-weight: bold;
    color: #fff;
    white-space: nowrap;
    background: linear-gradient(0deg, rgba(76, 143, 124, 1) 12%, rgba(95, 183, 158, 1) 100%);
    padding-top: 7px;
    padding-left: 20px;
}

/* Error box / toast (setErrorBox, dsToast).
   Fixed, not absolute: at position: absolute it sat 80px from the top of the
   *document*, so anything raised further down a scrolled page appeared above
   the viewport and was never seen. That did not matter while these messages
   were window.alert(), which always showed; it does now that they are toasts.
   Above the sheets too — a failure message must not be hidden by whatever
   dialog triggered it. */
.errorBox {
    position: fixed;
    top: 80px;
    left: 50%;
    transform: translateX(-50%);
    max-width: 90vw;
    background-color: #ff8585;
    font-weight: 900;
    font-size: 1rem;
    border: 1px solid #ff4242;
    border-radius: 12px;
    vertical-align: middle;
    padding: 16px;
    display: none;
    z-index: 10002;
    text-align: center;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
}
.fullscreenContent {
    position: absolute;
    top: 67px;
    left: 0px;
}

/* ===========================================================================
   Unified mobile design system ("Min profil" look).
   Page scaffold + grouped cards + list/field rows + sticky CTA + action sheet.
   Colours come from the --ds-* tokens (themed for light/dark).
   =========================================================================== */

/* A .ds-page paints its own light surface (--ds-surface) but lives inside the
   layout's Bootstrap .container, which adds 15px gutters (and a centred
   max-width from 576px up), and the top bar leaves a 10px strip below itself.
   Without this rule the body's --bg shows through on all four sides and the
   surface reads as a floating box with visible edges instead of one continuous
   page. Colour the body to match so the gutters, the strip under the top bar
   (whose shadow then falls onto the surface) and any area below short content
   all blend in. Pages that already set this locally are unaffected. */
body:has(.ds-page) {
    background-color: var(--ds-surface);
}

.ds-page {
    background: var(--ds-surface);
    color: var(--ds-ink);
    min-height: 100%;
    position: relative;
    padding-top: calc(8px + env(safe-area-inset-top, 0px));
    padding-bottom: 96px;
    font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
}
/* Pages with a sticky CTA bar need extra room above the bottom tab bar. */
.ds-page--footer { padding-bottom: calc(150px + env(safe-area-inset-bottom, 0px)); }

/* App bar */
.ds-appbar { padding: 12px 20px 6px; display: flex; align-items: center; gap: 12px; }
/* App-bar back button — raised white "puck" with a bold chevron (design handoff).
   The chevron is a CSS mask of the exact handoff SVG path, tinted via the ink
   token, so every existing `<a class="ds-back">&lsaquo;</a>` upgrades at once
   (the legacy glyph is hidden via font-size:0). */
.ds-back {
    width: 40px; height: 40px; flex: none; border-radius: 50%;
    background: var(--ds-paper);
    border: 1.5px solid var(--ds-rule);
    box-shadow: 0 2px 7px rgba(15, 31, 61, 0.12);
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; text-decoration: none;
    transition: transform 0.12s;
    font-size: 0; color: transparent;
}
.ds-back::before {
    content: "";
    width: 12px; height: 19px;
    background-color: var(--ds-ink);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='19' viewBox='0 0 12 19' fill='none'%3E%3Cpath d='M9.5 2L3 9.5l6.5 7.5' stroke='%23000' stroke-width='2.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='19' viewBox='0 0 12 19' fill='none'%3E%3Cpath d='M9.5 2L3 9.5l6.5 7.5' stroke='%23000' stroke-width='2.7' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.ds-back:active { transform: scale(0.9); }
.ds-back:focus-visible { box-shadow: 0 0 0 3px rgba(8, 113, 206, 0.35); outline: none; }

/* Frosted dark variant (dark theme / immersive surfaces). */
:root[data-theme="dark"] .ds-back {
    background: rgba(40, 40, 46, 0.55);
    border: 1px solid rgba(255, 255, 255, 0.2);
    box-shadow: none;
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
}
:root[data-theme="dark"] .ds-back::before { background-color: #FFFFFF; }

/* Generic app-bar icon button (same puck shape, holds an icon/img, no chevron).
   Used for non-back header controls e.g. the TripInMap visibility toggle. */
.ds-appbar-icon {
    width: 40px; height: 40px; flex: none; border-radius: 50%;
    background: var(--ds-paper); color: var(--ds-ink);
    border: 1.5px solid var(--ds-rule);
    box-shadow: 0 2px 7px rgba(15, 31, 61, 0.12);
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; text-decoration: none;
    transition: transform 0.12s;
}
.ds-appbar-icon:active { transform: scale(0.9); }
.ds-appbar-icon img, .ds-appbar-icon svg { width: 18px; height: 18px; }
.ds-appbar-title { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; flex: 1; }
.ds-appbar-spacer { width: 40px; }
/* Primary "add" action in the app bar — solid accent circle with a white plus. */
.ds-appbar-add {
    width: 40px; height: 40px; flex: none;
    border-radius: 50%;
    background: var(--ds-accent); color: #fff;
    display: flex; align-items: center; justify-content: center;
    cursor: pointer; text-decoration: none;
    box-shadow: 0 2px 8px rgba(8, 113, 206, 0.35);
}
.ds-appbar-add:active { transform: scale(0.92); transition: transform 0.08s ease; }

/* The app-bar pucks are drawn at 38–40px, below the 44pt (Apple HIG) / 48dp
   (Material) minimum touch target — and .ds-back is the primary navigation
   control on most pages. Extend the hit area past the visible edge with a
   transparent ::after instead of enlarging the pucks, so the design is
   unchanged but a thumb landing just outside still registers.
   .ds-back::before is taken by the chevron mask, hence ::after here. */
.ds-back, .ds-appbar-icon, .ds-appbar-add {
    position: relative;
}
.ds-back::after, .ds-appbar-icon::after, .ds-appbar-add::after {
    content: "";
    position: absolute;
    /* 40px + 2×4px = 48px, the Material minimum, for all three. */
    inset: -4px;
    border-radius: 50%;
}

/* Group label + hint + card */
.ds-group-label {
    padding: 16px 22px 7px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ds-ink3);
}
.ds-hint { padding: 0 22px 9px; font-size: 12.5px; line-height: 1.45; color: var(--ds-ink3); }
.ds-group {
    margin: 0 14px; background: var(--ds-paper);
    border: 1px solid var(--ds-rule); border-radius: 16px; overflow: hidden;
}

/* Shared row utilities */
.ds-last { border-bottom: none !important; }

/* List / setting / link rows */
.ds-row { display: flex; align-items: center; gap: 13px; padding: 13px 16px; border-bottom: 1px solid var(--ds-rule); cursor: pointer; }
.ds-row:active { background: var(--ds-accent-tint); }
/* The tile owns the glyph colour; its <svg> strokes use currentColor. Those
   were hardcoded #0871CE, which does not follow --ds-accent to its lighter
   dark-mode value (#4EA3EF) and left the icons dim against the dark tint. */
.ds-row-icon { width: 32px; height: 32px; flex: none; border-radius: 9px; background: var(--ds-accent-tint); color: var(--ds-accent); display: flex; align-items: center; justify-content: center; }
.ds-row-icon--danger { background: var(--ds-danger-tint); color: var(--ds-danger); }
.ds-row-body { flex: 1; min-width: 0; }
.ds-row-title { font-size: 15px; font-weight: 600; color: var(--ds-ink); letter-spacing: -0.005em; }
.ds-row-title--danger { color: var(--ds-danger); }
.ds-row-sub { font-size: 12px; color: var(--ds-ink3); margin-top: 1px; }
.ds-row-value { font-size: 14px; color: var(--ds-ink3); text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 42vw; }
.ds-row-value b { font-weight: 600; color: var(--ds-ink); }
.ds-chevron { color: var(--ds-chevron); font-size: 20px; flex: none; }
.ds-toggle { flex: none; display: flex; align-items: center; }
/* Pin both axes. With height alone the width came from the PNG's intrinsic
   size, which the browser only knows once the image has loaded — and .ds-toggle
   is flex: none, so the column was zero-wide on first paint and snapped to ~44px
   when the image arrived, jolting every settings row. The two states are also
   different sizes (toogle_on 54x32, toogle_off 56x32), so switching one nudged
   its row by ~2px. object-fit keeps both letterboxed in the fixed box instead of
   stretching them to it. */
.ds-toggle img { width: 45px; height: 26px; object-fit: contain; border: 0; }

/* Battery level glyph — the charge a voltage sensor has left, drawn the way the
   phone's own status bar draws it, so a list of five sensors can be scanned
   without reading the numbers. The percentage above it stays the accessible
   value; the SVG is aria-hidden decoration. Emitted by Sensors.cshtml.cs
   (BatterySvg), which sets the fill width and the state class. */
.ds-bat { width: 26px; height: 14px; flex: none; color: var(--ds-accent); overflow: visible; }
.ds-bat-shell { fill: none; stroke: currentColor; stroke-width: 1.2; opacity: 0.4; }
.ds-bat-cap { fill: currentColor; opacity: 0.4; }
.ds-bat-fill { fill: currentColor; }
.ds-bat--low { color: var(--ds-warm); }
.ds-bat--critical { color: var(--ds-danger); }

/* Editable fields */
.ds-field { padding: 10px 16px 11px; border-bottom: 1px solid var(--ds-rule); }
.ds-field-label {
    display: block;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ds-ink3);
    margin-bottom: 3px;
}
.ds-field-input {
    width: 100%; box-sizing: border-box;
    border: none; outline: none; background: transparent;
    font-size: 16px; font-weight: 500; color: var(--ds-ink);
    font-family: inherit; padding: 0;
    caret-color: var(--ds-accent);
}
textarea.ds-field-input { min-height: 92px; resize: vertical; line-height: 1.5; }

/* Radio indicator for single-select option rows (e.g. journey visibility) */
.ds-radio { width: 22px; height: 22px; flex: none; border-radius: 50%; border: 2px solid var(--ds-rule); box-sizing: border-box; position: relative; }
.ds-radio.on { border-color: var(--ds-accent); }
.ds-radio.on::after { content: ""; position: absolute; inset: 4px; border-radius: 50%; background: var(--ds-accent); }

/* Inline error banner */
.ds-error { margin: 12px 16px 0; padding: 11px 14px; border-radius: 12px; background: var(--ds-danger-tint); color: var(--ds-danger); font-size: 13.5px; font-weight: 600; text-align: center; }

/* "Add" affordance row */
.ds-add { display: flex; align-items: center; gap: 12px; padding: 13px 16px; cursor: pointer; }
.ds-add-icon { width: 30px; height: 30px; border-radius: 8px; background: var(--ds-accent-tint-soft); color: var(--ds-accent); display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 300; }
.ds-add-text { font-size: 15px; font-weight: 600; color: var(--ds-accent); }

/* Media row (icon + name + action), e.g. dog list */
.ds-media { display: flex; align-items: center; gap: 12px; padding: 13px 16px; border-bottom: 1px solid var(--ds-rule); }
.ds-media-icon { width: 30px; height: 30px; border-radius: 8px; background: var(--ds-accent-tint); display: flex; align-items: center; justify-content: center; font-size: 15px; }
.ds-media-name { flex: 1; font-size: 15px; font-weight: 500; }
.ds-media-action { color: var(--ds-ink3); font-size: 14px; cursor: pointer; padding: 4px; }

/* Identity header (avatar + name).
   The avatar scales with the viewport instead of sitting at a fixed 92px, which
   read as small next to full-width cards on a phone: ~112px on a 320px screen,
   ~133px on a 390px one, capped at 156px so it doesn't dominate a tablet. One
   custom property drives the wrap and the box so they can't drift apart. */
.ds-identity { text-align: center; padding: 14px 0 22px; --ds-avatar-size: clamp(112px, 34vw, 156px); }
.ds-avatar-wrap {
    position: relative;
    width: var(--ds-avatar-size, 92px); height: var(--ds-avatar-size, 92px);
    margin: 0 auto 14px; cursor: pointer;
}
.ds-avatar-box {
    width: 100%; height: 100%; border-radius: 50%; overflow: hidden;
    border: 3px solid var(--ds-paper); box-sizing: border-box;
    background: var(--ds-accent-tint-soft);
    box-shadow: 0 0 0 1px var(--ds-rule), 0 8px 22px rgba(8,113,206,0.20);
}
.ds-avatar { width: 100%; height: 100%; object-fit: cover; display: block; }
.ds-avatar-badge {
    position: absolute; right: -2px; bottom: -2px; width: 36px; height: 36px; border-radius: 50%;
    background: var(--ds-accent); border: 3px solid var(--ds-surface);
    display: flex; align-items: center; justify-content: center;
}
/* Overrides the inline width/height on the camera glyph so it keeps pace with
   the larger badge. */
.ds-avatar-badge svg { width: 17px; height: 17px; }
.ds-identity-name { font-size: 20px; font-weight: 700; letter-spacing: -0.02em; }
.ds-identity-mail { font-size: 13px; color: var(--ds-ink3); margin-top: 2px; }

/* Sticky CTA bar (sits just above the bottom tab bar) */
.ds-footer {
    position: fixed; left: 0; right: 0; z-index: 9971;
    bottom: calc(56px + env(safe-area-inset-bottom, 0px));
    padding: 14px 16px;
    background: var(--ds-surface);
    border-top: 1px solid var(--ds-rule);
}
.ds-cta {
    width: 100%; height: 50px; border: none; border-radius: 14px;
    font-size: 15px; font-weight: 600; letter-spacing: -0.005em;
    font-family: inherit; transition: background 0.2s;
    background: var(--ds-cta-disabled); color: var(--ds-cta-disabled-ink); cursor: default;
}
.ds-cta--active { background: var(--ds-cta); color: #fff; cursor: pointer; }
.ds-cta--saved { background: var(--ds-ok); color: #fff; cursor: default; }
.ds-cta-inner { display: inline-flex; align-items: center; gap: 8px; }

/* Bottom action sheet.
   These sit above every other surface in the app because anything can raise a
   confirmation — including the immersive photo viewer (.pc-viewer, 9995), which
   at the sheet's old 9990/9991 painted straight over it: "Slett bilde" appeared
   to do nothing, and the sheet was only revealed once the viewer was closed.
   Only the page-exit fade (#nx-kart-fade, 100000) is deliberately higher. */
.ds-scrim { display: none; position: fixed; inset: 0; z-index: 10000; background: rgba(15,31,61,0.4); backdrop-filter: blur(2px); }
.ds-sheet {
    display: none; position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 10001;
    background: var(--ds-paper);
    backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
    border-radius: 22px; padding: 8px 8px 10px;
}
.ds-sheet-handle { width: 38px; height: 4px; border-radius: 2px; background: var(--ds-rule); margin: 4px auto 8px; }
.ds-sheet-title {
    text-align: center; font-size: 12px; color: var(--ds-ink3); padding: 4px 0 10px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.08em; text-transform: uppercase;
}
.ds-sheet-btn { text-align: center; padding: 14px; font-size: 16px; font-weight: 600; color: var(--ds-accent); border-top: 1px solid var(--ds-rule); cursor: pointer; }
.ds-sheet-btn--danger { color: var(--ds-danger); }
.ds-sheet-cancel { text-align: center; padding: 14px; font-size: 16px; font-weight: 700; color: var(--ds-ink); background: var(--ds-surface); border-radius: 14px; margin-top: 8px; cursor: pointer; }
/* Confirmation message line for the shared dsConfirm() sheet (replaces window.confirm). */
.ds-sheet-msg { text-align: center; padding: 2px 14px 14px; font-size: 15px; line-height: 1.4; color: var(--ds-ink); }

/* ===========================================================================
   Photo Collection ("Fotoalbum") redesign.
   Cover-card album grid, photo grid + multi-select, immersive dark fullscreen
   viewer, create-album toggle, and the multi-photo upload queue.
   Built on the --ds-* tokens so it themes light/dark with the rest of the app.
   The fullscreen viewer is intentionally dark in both themes (photos pop on
   black), so it uses literal colours rather than tokens.
   =========================================================================== */

/* ── Album cover grid (Gallery / Gallery_Shared) ── */
.pc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 13px; padding: 0 16px; }
.pc-new-card {
    border: 1.5px dashed var(--ds-rule); border-radius: 16px; background: var(--ds-accent-tint-soft);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    min-height: 156px; gap: 8px; cursor: pointer; text-decoration: none;
}
.pc-new-plus {
    width: 42px; height: 42px; border-radius: 50%; background: var(--ds-accent); color: #fff;
    display: flex; align-items: center; justify-content: center; font-size: 26px; font-weight: 300; line-height: 1;
}
.pc-new-text { font-size: 14px; font-weight: 600; color: var(--ds-accent); }
.pc-card { cursor: pointer; text-decoration: none; display: block; }
.pc-cover {
    position: relative; border-radius: 16px; overflow: hidden; aspect-ratio: 1 / 1;
    background: var(--ds-accent-tint-soft);
    box-shadow: 0 4px 14px rgba(15,31,61,0.12), 0 0 0 1px rgba(15,31,61,0.04);
}
.pc-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pc-cover-shade {
    position: absolute; inset: 0;
    background: linear-gradient(180deg, rgba(0,0,0,0.22) 0%, rgba(0,0,0,0) 38%, rgba(0,0,0,0) 60%, rgba(0,0,0,0.35) 100%);
}
.pc-count-pill {
    position: absolute; top: 9px; right: 9px; padding: 3px 9px; border-radius: 9px;
    background: rgba(8,16,30,0.5); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    color: #fff; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.pc-badge {
    position: absolute; top: 9px; left: 9px; width: 26px; height: 26px; border-radius: 50%;
    background: rgba(8,16,30,0.42); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    display: flex; align-items: center; justify-content: center;
}
.pc-owner-pill {
    position: absolute; bottom: 9px; left: 9px; display: flex; align-items: center; gap: 6px;
    padding: 3px 9px 3px 3px; border-radius: 999px;
    background: rgba(8,16,30,0.42); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    color: #fff; font-size: 12px; font-weight: 600;
}
.pc-owner-dot {
    width: 20px; height: 20px; border-radius: 50%; background: var(--ds-warm); color: #fff;
    display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700;
}
.pc-meta { padding: 9px 2px 0; }
.pc-card-title { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.2; color: var(--ds-ink); }
.pc-card-sub { font-size: 12px; color: var(--ds-ink3); margin-top: 2px; }

/* ── Album detail: photo grid + multi-select (Gallery_Collection) ── */
.pc-subhead { display: flex; align-items: center; justify-content: space-between; padding: 0 18px 12px; }
.pc-meta-left { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--ds-ink3); }
.pc-meta-count { font-weight: 600; color: var(--ds-ink2); }
.pc-share-chip {
    display: flex; align-items: center; gap: 5px; padding: 5px 11px; border-radius: 999px;
    background: var(--ds-accent-tint); color: var(--ds-accent); font-size: 12px; font-weight: 600; cursor: pointer; border: none;
}
.pc-share-chip--off { background: var(--ds-surface); color: var(--ds-ink3); box-shadow: inset 0 0 0 1px var(--ds-rule); }
.pc-photo-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 0 12px; }
.pc-cell { position: relative; cursor: pointer; }
.pc-thumb {
    width: 100%; aspect-ratio: 1 / 1; border-radius: 11px; object-fit: cover; display: block;
    background: var(--ds-accent-tint-soft);
}
.pc-cover-tag {
    position: absolute; bottom: 7px; left: 7px; padding: 2px 8px; border-radius: 7px;
    background: rgba(8,16,30,0.5); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    color: #fff; font-size: 10.5px; font-weight: 700; letter-spacing: 0.02em;
}
.pc-sel-veil {
    position: absolute; inset: 0; border-radius: 11px; background: rgba(8,113,206,0.28);
    box-shadow: inset 0 0 0 3px var(--ds-accent); opacity: 0; transition: opacity .12s; pointer-events: none;
}
.pc-check {
    position: absolute; top: 7px; right: 7px; width: 22px; height: 22px; border-radius: 50%;
    background: rgba(8,16,30,0.28); border: 2px solid rgba(255,255,255,0.85);
    display: none; align-items: center; justify-content: center; box-shadow: 0 1px 3px rgba(0,0,0,0.25);
}
.pc-check svg { opacity: 0; }
.pc-selecting .pc-check { display: flex; }
.pc-cell.is-sel .pc-sel-veil { opacity: 1; }
.pc-cell.is-sel .pc-check { background: var(--ds-accent); border: 2px solid #fff; }
.pc-cell.is-sel .pc-check svg { opacity: 1; }
.pc-fab {
    position: fixed; left: 50%; transform: translateX(-50%);
    bottom: calc(76px + env(safe-area-inset-bottom, 0px)); z-index: 60;
    display: flex; align-items: center; gap: 9px; padding: 13px 22px; border-radius: 999px;
    background: var(--ds-cta); color: #fff; font-size: 15px; font-weight: 600; cursor: pointer;
    box-shadow: 0 8px 22px rgba(6,50,86,0.34); white-space: nowrap; text-decoration: none;
}
.pc-actionbar {
    position: fixed; left: 0; right: 0; z-index: 9971;
    bottom: calc(56px + env(safe-area-inset-bottom, 0px));
    padding: 12px 24px; background: var(--ds-surface); border-top: 1px solid var(--ds-rule);
    display: flex; justify-content: space-around;
}
.pc-act-btn { display: flex; flex-direction: column; align-items: center; gap: 4px; cursor: pointer; background: none; border: none; }
.pc-act-btn:disabled { opacity: 0.45; cursor: default; }
.pc-act-label { font-size: 11px; font-weight: 600; color: var(--ds-accent); }
.pc-act-label--danger { color: var(--ds-danger); }

/* Empty-album state */
.pc-empty { text-align: center; padding: 60px 30px 30px; color: var(--ds-ink3); }
.pc-empty-title { font-size: 16px; font-weight: 600; color: var(--ds-ink2); margin-bottom: 6px; }
.pc-empty-sub { font-size: 13px; line-height: 1.45; }

/* ── Immersive fullscreen viewer (dark in both themes) ── */
.pc-viewer { display: none; position: fixed; inset: 0; background: #000; overflow: hidden; z-index: 9995; }
.pc-viewer.is-open { display: block; }
.pc-stage { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: #000; }
.pc-bigimg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; z-index: 2; }
.pc-tapzone { position: absolute; top: 110px; bottom: 150px; width: 34%; z-index: 3; cursor: pointer; }
.pc-tapzone--left { left: 0; } .pc-tapzone--right { right: 0; }
.pc-topbar {
    position: absolute; top: 0; left: 0; right: 0; z-index: 10;
    padding: calc(20px + env(safe-area-inset-top, 0px)) 16px 0;
    display: flex; align-items: center; justify-content: space-between;
}
.pc-glassbtn {
    width: 38px; height: 38px; border-radius: 50%;
    background: rgba(40,40,46,0.5); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    border: 0.5px solid rgba(255,255,255,0.18);
    display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.pc-counter {
    color: #fff; font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums;
    padding: 6px 14px; border-radius: 999px;
    background: rgba(40,40,46,0.5); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}
.pc-counter span { opacity: 0.55; }
.pc-arrow {
    position: absolute; top: 50%; transform: translateY(-50%); z-index: 9;
    width: 40px; height: 40px; border-radius: 50%;
    background: rgba(40,40,46,0.4); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
    color: #fff; font-size: 26px; font-weight: 300; cursor: pointer;
    display: flex; align-items: center; justify-content: center; border: 0.5px solid rgba(255,255,255,0.14);
}
.pc-arrow--left { left: 10px; } .pc-arrow--right { right: 10px; }
.pc-vbottom {
    position: absolute; left: 0; right: 0; bottom: 0; z-index: 10;
    padding-top: 30px; padding-bottom: calc(26px + env(safe-area-inset-bottom, 0px));
    background: linear-gradient(0deg, rgba(0,0,0,0.78) 30%, rgba(0,0,0,0) 100%);
}
.pc-caption { color: rgba(255,255,255,0.78); font-size: 13px; font-weight: 500; text-align: center; padding-bottom: 12px; }
.pc-strip { display: flex; gap: 6px; overflow-x: auto; padding: 0 16px; scrollbar-width: none; }
.pc-strip::-webkit-scrollbar { display: none; }
.pc-strip-thumb {
    flex-shrink: 0; width: 42px; height: 42px; border-radius: 7px; overflow: hidden; cursor: pointer;
    border: 2px solid transparent; opacity: 0.6; transition: all .15s;
}
.pc-strip-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pc-strip-thumb.is-active { width: 52px; height: 52px; border: 2px solid #fff; opacity: 1; }
.pc-vscrim { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 80; }
.pc-vscrim.is-open { display: block; }
.pc-vsheet {
    display: none; position: absolute; left: 8px; right: 8px; z-index: 90;
    bottom: calc(8px + env(safe-area-inset-bottom, 0px));
    background: rgba(30,32,38,0.92); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px);
    border-radius: 22px; padding: 8px 6px 10px; border: 0.5px solid rgba(255,255,255,0.12);
}
.pc-vsheet.is-open { display: block; }
.pc-vsheet-handle { width: 38px; height: 4px; border-radius: 2px; background: rgba(255,255,255,0.25); margin: 4px auto 6px; }
.pc-vsheet-row { display: flex; align-items: center; gap: 14px; padding: 14px; border-bottom: 0.5px solid rgba(255,255,255,0.1); cursor: pointer; }
.pc-vsheet-row.pc-vsheet-last { border-bottom: none; }
.pc-vsheet-icon { width: 34px; height: 34px; border-radius: 9px; background: rgba(8,113,206,0.18); display: flex; align-items: center; justify-content: center; flex: none; }
.pc-vsheet-icon--danger { background: rgba(200,68,59,0.16); }
.pc-vsheet-label { font-size: 16px; font-weight: 600; color: #F3F6FB; }
.pc-vsheet-label--danger { color: #E0635A; }
.pc-vsheet-cancel { text-align: center; padding: 15px; font-size: 16px; font-weight: 700; color: #fff; background: rgba(120,120,128,0.24); border-radius: 14px; margin-top: 8px; cursor: pointer; }

/* ── Create album: share toggle ── */
.pc-toggle-row { display: flex; align-items: center; gap: 13px; padding: 14px 16px; cursor: pointer; }
.pc-toggle-icon { width: 34px; height: 34px; border-radius: 9px; background: var(--ds-accent-tint-soft); display: flex; align-items: center; justify-content: center; flex: none; transition: background .15s; }
.pc-toggle-icon.is-on { background: var(--ds-accent); }
.pc-toggle-body { flex: 1; }
.pc-toggle-title { font-size: 15px; font-weight: 600; letter-spacing: -0.005em; color: var(--ds-ink); }
.pc-toggle-sub { font-size: 12px; color: var(--ds-ink3); margin-top: 2px; line-height: 1.3; }
.pc-switch { width: 50px; height: 30px; border-radius: 999px; background: #CBD5E4; position: relative; flex: none; transition: background .18s; }
.pc-switch.is-on { background: var(--ds-accent); }
.pc-knob { position: absolute; top: 3px; left: 3px; width: 24px; height: 24px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.25); transition: left .18s; }
.pc-switch.is-on .pc-knob { left: 23px; }
:root[data-theme="dark"] .pc-switch { background: #3A4250; }

/* ── Multi-photo upload queue (Gallery_UploadImage) ── */
.pc-up-sub { padding: 0 22px 14px; font-size: 14px; color: var(--ds-ink3); }
.pc-up-sub strong { color: var(--ds-ink2); }
.pc-drop {
    margin: 0 16px; border-radius: 16px; padding: 26px 16px; border: 1.5px dashed var(--ds-rule);
    background: var(--ds-paper); display: flex; flex-direction: column; align-items: center; gap: 9px;
    cursor: pointer; transition: background .15s, border-color .15s;
}
.pc-drop.is-drag { border-color: var(--ds-accent); background: var(--ds-accent-tint-soft); }
.pc-drop-icon { width: 52px; height: 52px; border-radius: 50%; background: var(--ds-accent-tint); display: flex; align-items: center; justify-content: center; }
.pc-drop-title { font-size: 15px; font-weight: 600; color: var(--ds-ink); text-align: center; }
.pc-drop-sub { font-size: 12px; color: var(--ds-ink3); }
.pc-queue-head { display: flex; align-items: baseline; justify-content: space-between; padding-right: 22px; padding-top: 4px; }
.pc-queue-stat { font-size: 12px; color: var(--ds-accent); font-weight: 600; }
.pc-up-list { margin: 0 14px; background: var(--ds-paper); border: 1px solid var(--ds-rule); border-radius: 16px; overflow: hidden; }
.pc-up-row { display: flex; align-items: center; gap: 12px; padding: 11px 13px; border-bottom: 1px solid var(--ds-rule); }
.pc-up-row:last-child { border-bottom: none; }
.pc-up-thumb { width: 46px; height: 46px; border-radius: 9px; flex: none; object-fit: cover; display: block; background: var(--ds-accent-tint-soft); }
.pc-up-body { flex: 1; min-width: 0; }
.pc-file-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 7px; }
.pc-file-name { font-size: 13.5px; font-weight: 600; color: var(--ds-ink2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pc-bar-track { height: 5px; border-radius: 3px; background: var(--ds-accent-tint-soft); overflow: hidden; }
.pc-bar-fill { height: 100%; border-radius: 3px; width: 0%; background: var(--ds-accent); transition: width .3s ease; }
.pc-bar-fill.is-done { background: var(--ds-ok); }
.pc-fail-row { display: flex; align-items: center; gap: 10px; }
.pc-fail-text { font-size: 12px; color: var(--ds-danger); font-weight: 600; }
.pc-retry { font-size: 12px; color: var(--ds-accent); font-weight: 700; cursor: pointer; }
.pc-remove { width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; cursor: pointer; flex: none; }
.pc-stat-done { width: 18px; height: 18px; border-radius: 50%; background: rgba(31,138,91,0.16); display: flex; align-items: center; justify-content: center; flex: none; }
.pc-stat-fail { width: 18px; height: 18px; border-radius: 50%; background: var(--ds-danger-tint); color: var(--ds-danger); font-size: 13px; font-weight: 800; display: flex; align-items: center; justify-content: center; flex: none; }
.pc-stat-pct { font-size: 11.5px; font-weight: 700; color: var(--ds-accent); font-variant-numeric: tabular-nums; flex: none; }

/* ===========================================================================
   Journeys (Reiser) redesign — map-cover grid (.jr-*)
   Built on the same --ds-* tokens, so it themes light/dark automatically.
   Mirrors the Photo Collection album grid (.pc-*). Variation: "Kartrutenett".
   =========================================================================== */

/* ── search + sort controls ── */
.jr-controls { padding: 4px 16px 6px; }
.jr-search {
    display: flex; align-items: center; gap: 8px;
    background: var(--ds-paper); border: 1px solid var(--ds-rule);
    border-radius: 13px; padding: 0 12px; height: 40px;
}
.jr-search svg { width: 17px; height: 17px; flex: none; color: var(--ds-ink3); }
.jr-search input {
    border: 0; outline: 0; background: transparent; flex: 1;
    font-family: inherit; font-size: 15px; color: var(--ds-ink); min-width: 0;
}
.jr-search input::placeholder { color: var(--ds-ink3); }
.jr-sortrow {
    display: flex; gap: 7px; padding: 11px 16px 4px; overflow-x: auto;
    scrollbar-width: none;
}
.jr-sortrow::-webkit-scrollbar { display: none; }
.jr-sort {
    flex: none; padding: 7px 14px; border-radius: 999px;
    font-size: 12.5px; font-weight: 600; cursor: pointer; white-space: nowrap;
    background: var(--ds-paper); color: var(--ds-ink2); border: 1px solid var(--ds-rule);
}
.jr-sort.active { background: var(--ds-cta); color: #fff; border-color: var(--ds-cta); }
.jr-count {
    margin-left: auto; align-self: center; font-size: 12px; color: var(--ds-ink3);
    font-weight: 600; flex: none; padding-left: 6px; white-space: nowrap;
}

/* ── month group label ── */
.jr-month {
    padding: 16px 22px 9px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ds-ink3);
    display: flex; align-items: baseline; gap: 8px;
}
.jr-month b { color: var(--ds-ink2); font-weight: 700; }
.jr-month .agg { margin-left: auto; letter-spacing: 0.04em; }

/* ── map-cover grid ── */
.jr-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 13px; padding: 0 16px; }
.jr-card { display: block; text-decoration: none; color: inherit; cursor: pointer; }
.jr-cover {
    position: relative; border-radius: 16px; overflow: hidden; aspect-ratio: 4 / 3;
    background: var(--ds-accent-tint-soft);
    background-size: cover; background-position: center; background-repeat: no-repeat;
    box-shadow: 0 4px 14px rgba(15,31,61,0.12), 0 0 0 1px rgba(15,31,61,0.04);
}
.jr-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.jr-glass {
    position: absolute; display: flex; align-items: center; gap: 4px;
    padding: 4px 9px; border-radius: 9px;
    background: rgba(8,16,30,0.5); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    color: #fff; font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.jr-glass svg { width: 12px; height: 12px; }
.jr-glass--dist { top: 9px; right: 9px; }
.jr-glass--share { top: 9px; left: 9px; padding: 4px; border-radius: 50%; width: 24px; height: 24px; box-sizing: border-box; justify-content: center; }
.jr-glass--dur { bottom: 9px; left: 9px; background: rgba(8,16,30,0.46); }
.jr-cardbody { padding: 9px 2px 0; }
.jr-card-title { font-size: 14.5px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.22; color: var(--ds-ink); }
.jr-card-sub { font-size: 12px; color: var(--ds-ink3); margin-top: 3px; }

/* ── "no results" inline message (search yields nothing) ── */
.jr-noresults { padding: 40px 24px; text-align: center; font-size: 14px; color: var(--ds-ink3); }

/* ── empty state (no journeys at all) ── */
.jr-empty { text-align: center; padding: 60px 34px 30px; }
.jr-empty-art {
    width: 96px; height: 96px; margin: 0 auto 20px; border-radius: 50%;
    background: var(--ds-accent-tint); display: flex; align-items: center; justify-content: center;
}
.jr-empty-art svg { width: 46px; height: 46px; color: var(--ds-accent); }
.jr-empty-title { font-size: 19px; font-weight: 700; letter-spacing: -0.01em; color: var(--ds-ink); }
.jr-empty-sub { font-size: 13.5px; line-height: 1.5; color: var(--ds-ink3); margin: 9px auto 24px; max-width: 250px; }
.jr-empty-cta {
    display: inline-flex; align-items: center; gap: 9px; padding: 13px 24px; border-radius: 14px;
    background: var(--ds-cta); color: #fff; font-size: 15px; font-weight: 600; cursor: pointer;
    box-shadow: 0 8px 22px rgba(6,50,86,0.28); border: none; font-family: inherit; text-decoration: none;
}
.jr-empty-cta svg { width: 18px; height: 18px; }

/* ===========================================================================
   Journey detail (Reise · detalj) — map hero + floating stat strip + route
   timeline (.jd-*). Same --ds-* tokens, themes light/dark automatically.
   =========================================================================== */
/* map hero — full-bleed at the top of the page, sits flush under the brand bar */
.jd-hero {
    /* Rounded like every other surface; overflow: hidden already clips the
       image and the gradient to it. */
    border-radius: 18px;
    position: relative; height: 300px; overflow: hidden;
    margin-top: calc(-8px - env(safe-area-inset-top, 0px));
    background-size: cover; background-position: center; background-repeat: no-repeat;
    cursor: pointer;
}
.jd-hero > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.jd-hero-grad { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(8,16,30,0.42) 0%, rgba(8,16,30,0) 22%, rgba(8,16,30,0) 34%, rgba(8,16,30,0.46) 64%, rgba(8,16,30,0.30) 84%, var(--ds-surface) 100%); }
.jd-hero-top { position: absolute; top: 0; left: 0; right: 0; display: flex; align-items: center; gap: 11px; padding: 12px 16px; }
.jd-iconbtn {
    width: 38px; height: 38px; flex: none; border-radius: 50%;
    background: rgba(255,255,255,0.92); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
    box-shadow: 0 2px 10px rgba(8,16,30,0.25);
    display: flex; align-items: center; justify-content: center; cursor: pointer; text-decoration: none;
}
.jd-iconbtn svg { width: 15px; height: 17px; color: var(--ds-ink); }
.jd-share-tag {
    margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
    padding: 7px 12px; border-radius: 999px; font-size: 12px; font-weight: 700; color: #fff;
    background: rgba(8,16,30,0.5); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}
.jd-share-tag svg { width: 13px; height: 13px; }

.jd-hero-tx { position: absolute; left: 18px; right: 18px; bottom: 16px; }
.jd-hero-title { font-size: 26px; font-weight: 800; letter-spacing: -0.02em; color: #fff; line-height: 1.1; text-shadow: 0 2px 10px rgba(0,0,0,0.4); }
.jd-hero-date { font-size: 13px; font-weight: 600; color: rgba(255,255,255,0.9); margin-top: 5px; text-shadow: 0 1px 6px rgba(0,0,0,0.45); }

.jd-body { padding: 0 16px; }

/* stat strip (floats up over hero edge) */
.jd-stats {
    display: flex; background: var(--ds-paper); border: 1px solid var(--ds-rule); border-radius: 16px;
    box-shadow: 0 6px 20px rgba(15,31,61,0.10); padding: 14px 6px; margin-top: -6px; position: relative; z-index: 2;
}
.jd-stat { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; position: relative; }
.jd-stat + .jd-stat::before { content: ""; position: absolute; left: 0; top: 8%; height: 84%; width: 1px; background: var(--ds-rule); }
.jd-stat-val { font-size: 17px; font-weight: 800; color: var(--ds-ink); font-variant-numeric: tabular-nums; display: flex; align-items: baseline; gap: 3px; }
.jd-stat-val small { font-size: 11px; font-weight: 700; color: var(--ds-ink3); }
.jd-stat-lbl { font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ds-ink3); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

.jd-section-h { font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ds-ink3); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 700; margin: 22px 4px 10px; }

.jd-desc { font-size: 14.5px; line-height: 1.55; color: var(--ds-ink2); padding: 0 4px; margin: 0; }

/* route timeline card */
.jd-route-card { background: var(--ds-paper); border: 1px solid var(--ds-rule); border-radius: 16px; padding: 4px 16px; }
.jd-leg { display: flex; gap: 13px; padding: 13px 0; position: relative; }
.jd-leg + .jd-leg { border-top: 1px solid var(--ds-rule); }
.jd-leg-rail { width: 14px; flex: none; display: flex; flex-direction: column; align-items: center; padding-top: 3px; position: relative; }
.jd-dot { width: 13px; height: 13px; border-radius: 50%; flex: none; box-sizing: border-box; }
.jd-dot.start { background: var(--ds-ok); box-shadow: 0 0 0 4px rgba(31,138,91,0.16); }
.jd-dot.via { background: var(--ds-paper); border: 2.5px solid var(--ds-accent); }
.jd-dot.end { background: var(--ds-danger); box-shadow: 0 0 0 4px rgba(200,68,59,0.16); }
.jd-leg-line { position: absolute; top: 16px; bottom: -13px; width: 2px; background: var(--ds-rule); }
.jd-leg:last-child .jd-leg-line { display: none; }
.jd-leg-body { flex: 1; min-width: 0; }
.jd-leg-time { font-size: 11px; font-weight: 700; color: var(--ds-accent); font-variant-numeric: tabular-nums; }
.jd-leg-place { font-size: 14.5px; font-weight: 700; color: var(--ds-ink); margin-top: 1px; }
.jd-leg-addr { font-size: 12.5px; color: var(--ds-ink3); margin-top: 2px; line-height: 1.35; }

/* action buttons */
.jd-actions { display: flex; gap: 11px; margin: 22px 0 8px; }
.jd-btn { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 50px; border-radius: 14px; font-size: 15px; font-weight: 700; cursor: pointer; border: 1.5px solid transparent; font-family: inherit; }
.jd-btn svg { width: 17px; height: 17px; }
.jd-btn.edit { background: var(--ds-cta); color: #fff; box-shadow: 0 6px 16px rgba(6,50,86,0.26); }
.jd-btn.del { background: var(--ds-paper); color: var(--ds-danger); border-color: var(--ds-rule); }
.jd-btn.gpx { width: 100%; margin-top: 22px; text-decoration: none; background: var(--ds-paper); color: var(--ds-ink); border-color: var(--ds-rule); }

/* ===========================================================================
   Reduced motion (WCAG 2.3.3).
   The app leans on animation everywhere — the dashboard alone runs 16 tiles of
   looping SVG, and the shell has fades, slides and tap-scales. Honour the OS
   "reduce motion" switch by collapsing every CSS animation and transition to a
   near-instant step rather than removing them, which keeps state changes
   visible without motion. SMIL (<animate> inside inline SVG) is out of CSS's
   reach and is paused separately in site.js.
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ===========================================================================
   Focus ring for the elements site.js promotes to keyboard-operable buttons
   (the app's clickable <div>/<td>). Without a visible ring, tabbing through a
   page is invisible — you cannot tell what Enter would activate. :focus-visible
   keeps it to keyboard use, so mouse and touch are unchanged. outline (not
   box-shadow) because it follows each element's own border-radius and never
   affects layout.
   =========================================================================== */
[data-nx-a11y="1"]:focus-visible {
    outline: 3px solid var(--ds-accent);
    outline-offset: 2px;
}

/* Toast (dsToast) — reuses .errorBox. Messages can carry newlines from server
   responses, so keep them; the info tone drops the red for status messages
   that are not failures. */
.errorBox {
    white-space: pre-line;
}
.errorBox--info {
    background-color: var(--ds-accent-tint);
    border-color: var(--ds-accent-mid);
    color: var(--ds-ink);
}

/* Text input inside the dsPrompt sheet. */
.ds-sheet-input {
    display: block;
    width: 100%;
    box-sizing: border-box;
    margin: 2px 0 10px;
    padding: 12px 14px;
    border: 1.5px solid var(--ds-rule);
    border-radius: 12px;
    background: var(--ds-surface);
    color: var(--ds-ink);
    font-family: inherit;
    font-size: 16px; /* under 16px iOS zooms the page on focus */
}
.ds-sheet-input:focus {
    outline: none;
    border-color: var(--ds-accent);
}
/* The prompt sheet uses real <button>s for its actions (the confirm sheet uses
   divs); strip the UA button chrome so both look identical. */
button.ds-sheet-btn, button.ds-sheet-cancel {
    display: block;
    width: 100%;
    border: 0;
    font-family: inherit;
}
button.ds-sheet-btn {
    background: transparent;
}

@keyframes nx-spin {
    to { transform: rotate(360deg); }
}

/* ===========================================================================
   Map surface.
   Maps carried a 1px #012152 hairline and a doubled 40%-black shadow — a hard
   rectangle that read as a different material from the soft cards the rest of
   the app is built from. Same card language instead: a generous radius, the
   design system's rule colour, and a light shadow in the system's ink tone.
   overflow: hidden makes the radius clip the map's own canvas, which paints
   over the corners otherwise. Deliberately no -webkit-mask clipping hack: on
   older engines that fails to square corners, which is cosmetic, where the
   hack risks blanking a WebGL canvas outright.
   =========================================================================== */
.ds-map {
    border-radius: 18px;
    overflow: hidden;
    border: 1px solid var(--ds-rule);
    box-shadow: 0 6px 20px rgba(15, 31, 61, 0.12);
    /* Shows through while tiles load, instead of a white flash. */
    background: var(--ds-accent-tint-soft);
}

/* Fill the space between the app bar and the bottom tab bar.
   Every map page stacks the same chrome — .fullscreenContent starts 67px down,
   its .ds-appbar row is ~56px, and the fixed tab bar takes ~55px plus the home
   indicator — so the arithmetic lives here once instead of five slightly
   different copies.
   svh, not vh: on a phone 100vh is the viewport with the browser chrome
   HIDDEN, so a vh-based map is taller than what you can actually see and its
   bottom edge — with Mapbox's and Google's zoom controls on it — disappears
   under the tab bar. svh is the chrome-visible height, so the bottom always
   clears. The plain vh line above it is the fallback for engines without svh. */
.ds-map--fill {
    height: calc(100vh - var(--nx-map-chrome));
    height: calc(100svh - var(--nx-map-chrome) - env(safe-area-inset-bottom, 0px));
}

/* ===========================================================================
   Loading indicators.
   The app had four: loading.gif, loading_blue_dots.gif, a 12vh-tall
   loading_blue_line.gif stretched across the viewport, and a CSS spinner — so
   which one you saw depended on which page you were waiting on. These two
   components plus images/loading.svg replace them. Being CSS, they follow the
   theme tokens, stay crisp at any density, and cost no image request.
   =========================================================================== */

/* Indeterminate top progress bar. Keeps the .loadingBlueLine class name: five
   pages toggle it by that name from JS, and the markup only loses its <img>. */
.loadingBlueLine {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    z-index: 9999;
    display: none;
    overflow: hidden;
    background: var(--ds-accent-tint);
}
.loadingBlueLine::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 40%;
    background: var(--ds-accent);
    animation: nx-progress 1.15s ease-in-out infinite;
}
@keyframes nx-progress {
    from { transform: translateX(-110%); }
    to { transform: translateX(360%); }
}

/* Ring spinner. Size with --ds-spinner-size. */
.ds-spinner {
    display: inline-block;
    width: var(--ds-spinner-size, 26px);
    height: var(--ds-spinner-size, 26px);
    border: 3px solid var(--ds-accent-tint);
    border-top-color: var(--ds-accent);
    border-radius: 50%;
    animation: nx-spin 0.7s linear infinite;
}
.ds-spinner--lg {
    --ds-spinner-size: 54px;
    border-width: 4px;
}
/* On the dark scrim of a blocking overlay the accent tint disappears. */
.loadingOverlay .ds-spinner {
    border-color: rgba(255, 255, 255, 0.3);
    border-top-color: #ffffff;
}

/* ===========================================================================
   Auth-flow buttons.
   .loginSubmitBtn and .sendlinkBtn were declared six times — byte-identical
   copies across Login, LoginSendLink and the four CreateAccount pages — so a
   change to the sign-in button had to be made six times or not at all. Defined
   once here.
   The pill shape is kept on purpose: these sit on a full-bleed photograph
   before the app shell exists, and read as their own surface. What they no
   longer keep is the hard 1px #000 outline.
   The fill is the light theme's --ds-cta navy in every theme: these buttons
   always sit on the blue .loginBox card (#0871CE), and dark theme's --ds-cta
   is that same blue, which made them vanish into the card.
   =========================================================================== */
.loginSubmitBtn, .sendlinkBtn {
    display: block;
    width: 225px;
    margin: 0 auto;
    border: none;
    border-radius: 30px;
    background-color: #063256;
    color: #ffffff;
    font-family: inherit;
    font-size: 1rem;
    text-align: center;
    cursor: pointer;
    transition: transform 0.08s ease, box-shadow 0.2s ease;
}
.loginSubmitBtn {
    height: 60px;
    font-weight: bold;
}
.sendlinkBtn {
    height: 50px;
    padding-top: 13px;
}
.loginSubmitBtn:active, .sendlinkBtn:active {
    transform: scale(0.97);
}
@media (hover: hover) {
    .loginSubmitBtn:hover, .sendlinkBtn:hover {
        box-shadow: 0 4px 29px 0 rgb(255 255 255 / 30%), 0 6px 38px 0 rgb(255 255 255 / 23%);
    }
}

/* Floating control over a map ("Ny venn", "Global", "Sett geofence").
   These were three copies of one legacy idea: a 45-70px box with a hard 1px
   border (#252525 on BuddyMap, #bbb on Map), no border-radius at all on the
   Map one, and an 8.4px label. Against the rounded map surface they read as
   leftovers. One component now — paper puck, the map's own corner radius,
   soft shadow, no outline — and each page keeps only its own positioning. */
.ds-mapbtn {
    width: 64px;
    min-height: 44px;
    padding: 7px 5px 6px;
    border: none;
    border-radius: 14px;
    background: var(--ds-paper);
    color: var(--ds-ink);
    box-shadow: 0 3px 12px rgba(15, 31, 61, 0.22);
    text-align: center;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    transition: transform 0.1s ease, box-shadow 0.2s ease;
}

/* Stack for the controls that sit over a map. Each button used to float right
   on its own, which puts two of them side by side on one line rather than one
   above the other, and the position: relative "top" nudge then staggered them
   into each other. Floating the stack instead — a single float, out of flow and
   anchored exactly where the buttons used to be — gives a clean column. Pages
   set only its top offset.
   Deliberately not position: absolute: nothing here establishes a containing
   block, so absolute would resolve against the document and drop the buttons
   into the blue header instead of onto the map. */
.ds-mapbtn-stack {
    float: right;
    position: relative;
    /* Clears the app bar. The stack floats in normal flow while the app bar
       lives inside the absolutely-positioned .fullscreenContent table, so the
       two occupy the same vertical band unless the stack is pushed past it:
       two occupy the same vertical band unless the stack is pushed past it.
       Measured on the live page. Too small a value puts the first button on
       top of the bar's right-hand control. */
    top: 66px;
    z-index: 9981;
    /* Pulls the column flush with the map's right edge, past the container
       gutter. */
    margin-right: -4px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
}
.ds-mapbtn:active {
    transform: scale(0.95);
}
@media (hover: hover) {
    .ds-mapbtn:hover {
        box-shadow: 0 5px 18px rgba(15, 31, 61, 0.3);
    }
}
.ds-mapbtn img, .ds-mapbtn svg {
    display: block;
    margin: 0 auto 3px;
    height: 26px;
    width: auto;
    border: 0;
}
