/* =====================================================================
   Default dashboard — iOS 26 / macOS Tahoe "Liquid Glass"
   ---------------------------------------------------------------------
   Restyles the staff + client dashboard (app/Views/dashboards/
   custom_dashboards/view.php) into one coherent glass surface:

     .nz-dash-bar      the floating, translucent navigation layer that
                       carries the dashboard title, its colour tags and
                       the dashboard menu. It sticks under the topbar,
                       condenses on scroll and refracts whatever the
                       page scrolls beneath it.
     .nz-dash-kpis     the KPI strip (the old .dashboard-icon-widget
                       tiles) re-cut as iOS "glass readouts": big
                       tabular figure, caption underneath, the icon in
                       a tinted glass plate on the right.
     .card             every widget card, given the iOS 26 material,
                       the platform title scale and a specular sheen
                       that tracks the pointer.

   Scoped to `.nz-dash26` (set on #page-content by the dashboard view)
   so nothing here reaches the rest of the app. Pairs with
   assets/js/dashboard-ios26.js, which sets --nz-tint per widget, drives
   the pointer sheen and condenses the bar.

   Colours come from the shared --ios26-* tokens in app.all.css, which
   body.color-1E202D re-declares — so dark mode needs no duplicated
   rules here. The handful of exceptions carry a body.color-1E202D
   prefix + !important, because assets/css/color/1E202D.css is injected
   at RUNTIME and wins every tie (see .claude/rules/frontend-ui.md).
   ===================================================================== */

.nz-dash26 {
    /* the KPI/widget accent; JS overrides it per card from the tile's
       own colour so the glass picks up the content beneath it */
    --nz-tint: var(--ios26-accent);
    --nz-tint-soft: var(--ios26-accent-soft);

    --nz-gap: 18px;
    --nz-title: 15px;

    /* HARD RULE: no dashboard card may be taller than a phone held in
       portrait — on desktop too. The bound is the iPhone 17 Pro Max
       portrait viewport: 956 CSS px (1320x2868 physical at @3x).
       Anything longer scrolls INSIDE the card instead of pushing the rest
       of the dashboard down the page.

       --nz-card-fit is the tighter height a CONTENT-HEAVY card settles at,
       so a widget with a thousand rows does not eat a whole screen just
       because it is allowed to. The ceiling is the rule; this is the
       rhythm. */
    /* Both are clamped by the VIEWPORT, not just by the breakpoint. The
       560px working height was measured on a desktop; on a phone held
       landscape (956x440) the width rules do not apply, so cards kept it
       and two of them came out taller than the whole screen. clamp() keeps
       a card inside whatever screen it is on, with a floor so a very short
       window shrinks them rather than crushing them. */
    --nz-card-max: clamp(240px, calc(100dvh - 120px), 956px);
    --nz-card-fit: clamp(240px, calc(100dvh - 120px), 560px);

    padding-bottom: 28px;
}

/* ===================================================== floating bar === */

.nz-dash26 .nz-dash-bar {
    position: sticky;
    top: 8px;
    z-index: 20;

    display: flex;
    align-items: center;
    gap: 14px;

    margin: 0 0 var(--nz-gap);
    padding: 12px 18px;

    border: 1px solid var(--ios26-edge);
    border-radius: var(--ios26-pill);
    background: var(--ios26-surface);
    -webkit-backdrop-filter: var(--ios26-blur);
    backdrop-filter: var(--ios26-blur);
    box-shadow: var(--ios26-inset), var(--ios26-shadow-1);

    transition: padding 0.32s var(--ios26-ease),
                box-shadow 0.32s var(--ios26-ease),
                background 0.32s var(--ios26-ease);
}

/* A custom dashboard has a colour of its own; until a widget scrolls
   under the bar, that is what it should be wearing. The JS overwrites
   --nz-tint as content passes, so this is only the resting state. */
.nz-dash26 .nz-dash-bar[style*="--nz-dash-color"] {
    --nz-tint: var(--nz-dash-color);
    --nz-tint-soft: color-mix(in srgb, var(--nz-dash-color) 14%, transparent);
}

/* the refraction: a soft tinted lens that sits *inside* the bar and
   picks up the accent of whatever is scrolling under it */
.nz-dash26 .nz-dash-bar::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;

    background:
        radial-gradient(120% 160% at 12% 0%, var(--nz-tint-soft), transparent 62%),
        linear-gradient(180deg, rgba(255, 255, 255, .22), transparent 58%);
    opacity: .9;
    transition: opacity 0.32s var(--ios26-ease);
}

/* The rim is where glass actually refracts: light bends at the edge of
   the capsule, brightest where it faces the light and fading round the
   curve, and it carries a trace of whatever colour is behind it. A
   gradient painted into a 1px ring (mask-composite cuts the middle out)
   reads as that edge; a plain border cannot. */
.nz-dash26 .nz-dash-bar::after {
    content: "";
    position: absolute;
    inset: 0;
    padding: 1px;
    border-radius: inherit;
    pointer-events: none;

    background: linear-gradient(
        135deg,
        rgba(255, 255, 255, 0.95) 0%,
        rgba(255, 255, 255, 0.35) 22%,
        rgba(255, 255, 255, 0.05) 45%,
        var(--nz-tint-soft) 72%,
        rgba(255, 255, 255, 0.55) 100%
    );

    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;

    transition: background 0.4s var(--ios26-ease);
}

body.color-1E202D .nz-dash26 .nz-dash-bar::after {
    background: linear-gradient(
        135deg,
        rgba(255, 255, 255, 0.28) 0%,
        rgba(255, 255, 255, 0.10) 22%,
        rgba(255, 255, 255, 0.02) 45%,
        var(--nz-tint-soft) 72%,
        rgba(255, 255, 255, 0.16) 100%
    );
}

/* condensed state — set by assets/js/dashboard-ios26.js past ~40px */
.nz-dash26 .nz-dash-bar.is-condensed {
    padding: 7px 16px;
    box-shadow: var(--ios26-inset), var(--ios26-shadow-2);
}

.nz-dash26 .nz-dash-bar.is-condensed .nz-dash-bar-title {
    font-size: 17px;
}

.nz-dash26 .nz-dash-bar.is-condensed .nz-dash-bar-sub {
    opacity: 0;
    max-height: 0;
    margin: 0;
}

.nz-dash26 .nz-dash-bar-text {
    min-width: 0;
    margin-right: auto;
}

.nz-dash26 .nz-dash-bar-title {
    display: block;
    margin: 0;

    /* One line, always. A dashboard can be named anything, and a long one
       wrapped: measured on a phone, an 88-character title made the bar
       178px tall across six lines — a floating navigation layer taking
       40% of the screen. iOS truncates a nav title; so does this. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 22px;
    font-weight: 700;
    line-height: 1.18;
    letter-spacing: -0.021em;
    color: var(--ios26-text);
    transition: font-size 0.32s var(--ios26-ease);
}

.nz-dash26 .nz-dash-bar-sub {
    display: block;
    margin: 2px 0 0;
    max-height: 22px;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
    font-size: 13px;
    line-height: 1.4;
    letter-spacing: -0.006em;
    color: var(--ios26-muted);
    transition: opacity 0.24s var(--ios26-ease),
                max-height 0.32s var(--ios26-ease),
                margin 0.32s var(--ios26-ease);
}

/* the dashboard colour tags, as iOS segmented dots */
.nz-dash26 .nz-dash-bar #dashboards-color-tags {
    display: flex;
    align-items: center;
    gap: 2px;
    float: none !important;
    padding: 3px;
    border-radius: var(--ios26-pill);
    background: var(--ios26-fill);
}

.nz-dash26 .nz-dash-bar #dashboards-color-tags a {
    display: inline-flex;
}

.nz-dash26 .nz-dash-bar #dashboards-color-tags .clickable {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0 !important;
    margin: 0 !important;
    border-radius: 50%;
    transition: background 0.2s var(--ios26-ease), transform 0.2s var(--ios26-spring);
}

.nz-dash26 .nz-dash-bar #dashboards-color-tags .clickable:hover {
    background: var(--ios26-fill-hover);
    transform: scale(1.06);
}

.nz-dash26 .nz-dash-bar .color-tag {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    /* the default dashboard's tag is #fff, which would disappear against
       the glass — give every tag a hairline so all of them read */
    box-shadow: 0 0 0 1px var(--ios26-hairline-strong);
}

.nz-dash26 .nz-dash-bar .color-tag.border-circle {
    box-shadow: 0 0 0 2px var(--ios26-accent), 0 0 0 4px var(--ios26-accent-ring);
}

.nz-dash26 .nz-dash-bar .dashboard-dropdown {
    float: none !important;
    margin: 0 !important;
}

.nz-dash26 .nz-dash-bar .dashboard-dropdown .dropdown-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    color: var(--ios26-muted);
    background: var(--ios26-fill);
    transition: background 0.2s var(--ios26-ease), color 0.2s var(--ios26-ease);
}

.nz-dash26 .nz-dash-bar .dashboard-dropdown .dropdown-toggle:hover {
    background: var(--ios26-fill-hover);
    color: var(--ios26-text);
}

.nz-dash26 .nz-dash-bar .dropdown-toggle::after {
    display: none;
}

/* ========================================================= KPI strip === */

.nz-dash26 .dashboard-icon-widget,
.nz-dash26 .card.dashboard-icon-widget {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--ios26-edge) !important;
    border-radius: var(--ios26-radius-lg) !important;
    background: var(--ios26-surface-strong) !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    box-shadow: var(--ios26-inset), var(--ios26-shadow-1) !important;
    transition: transform 0.35s var(--ios26-spring),
                box-shadow 0.35s var(--ios26-ease);
}

/* the tint the tile takes from its own icon colour (set by the JS) */
.nz-dash26 .dashboard-icon-widget::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(130% 150% at 88% 0%, var(--nz-tint-soft), transparent 60%);
}

.nz-dash26 a:hover .dashboard-icon-widget {
    transform: translateY(-2px);
    box-shadow: var(--ios26-inset), var(--ios26-shadow-2) !important;
}

/* a KPI tile is a readout, not a panel: it never grows to match a taller
   card in the same band */
.nz-dash26 .dashboard-icon-widget {
    max-height: 132px;
}

.nz-dash26 .dashboard-icon-widget .card-body {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 18px !important;
    background: transparent !important;
}

/* app.all.css takes .widget-details OUT OF FLOW (`position: absolute;
   right: 20px`) and floats .widget-icon under it. That only looks right
   while the tile is wide: narrow it and the figure runs off the left edge
   with the icon on top of it. Put both back in the flex flow. */
.nz-dash26 .dashboard-icon-widget .widget-details {
    order: 1;
    position: static !important;
    right: auto !important;
    min-width: 0;
    flex: 1 1 auto;
    text-align: left !important;
    padding: 0 !important;
}

.nz-dash26 .dashboard-icon-widget .widget-icon {
    float: none !important;
}

.nz-dash26 .dashboard-icon-widget .widget-details h1 {
    margin: 0;
    font-size: 28px;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.024em;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    color: var(--ios26-text) !important;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nz-dash26 .dashboard-icon-widget .widget-details span,
.nz-dash26 .dashboard-icon-widget .widget-details .bg-transparent-white {
    display: block;
    margin-top: 3px;
    padding: 0 !important;
    font-size: 13px;
    font-weight: 500;
    line-height: 1.35;
    letter-spacing: -0.004em;
    color: var(--ios26-muted) !important;
    background: transparent !important;
}

/* the icon plate — a small piece of tinted glass, not a solid block */
.nz-dash26 .dashboard-icon-widget .widget-icon {
    order: 2;
    display: flex !important;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 44px;
    height: 44px;
    padding: 0 !important;
    border: 1px solid var(--ios26-edge);
    border-radius: var(--ios26-radius);
    box-shadow: var(--ios26-inset);
}

.nz-dash26 .dashboard-icon-widget .widget-icon i {
    font-size: 19px;
    line-height: 1;
    width: auto !important;
    height: auto !important;
}

/* The tint is painted only AFTER the JS has read the widget's own colour
   (it sets data-nz-tint). Painting it unconditionally made the whole thing
   circular: the plate was given var(--nz-tint), which defaults to the app
   accent, so every card then "found" the accent and tinted itself with it —
   one blue dashboard that looked adaptive and was not. */
.nz-dash26 .card[data-nz-tint] .widget-icon {
    background: var(--nz-tint-soft) !important;
    color: var(--nz-tint) !important;
}

.nz-dash26 .card[data-nz-tint] .widget-icon i {
    color: var(--nz-tint) !important;
}

/* ====================================================== widget cards === */

.nz-dash26 .dashboards-row {
    --bs-gutter-x: var(--nz-gap);
    align-items: stretch;
    margin-bottom: 0;
}

/* every card in a band ends on the same line: the column stretches to the
   band height, and the card fills the column whether the widget wraps it
   in a container of its own or not */
.nz-dash26 .widget-container {
    display: flex;
    flex-direction: column;
    margin-bottom: var(--nz-gap);
}

.nz-dash26 .widget-container > .card,
.nz-dash26 .widget-container > div,
.nz-dash26 .widget-container > a {
    flex: 1 1 auto;
    min-height: 0;
}

.nz-dash26 .widget-container > div > .card,
.nz-dash26 .widget-container > a > .card {
    height: 100%;
}

/* a column holding two stacked widgets shares the height between them */
.nz-dash26 .widget-container > .card + .card,
.nz-dash26 .widget-container > div + div {
    margin-top: var(--nz-gap);
}

.nz-dash26 .card {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--ios26-edge) !important;
    border-radius: var(--ios26-radius-lg) !important;

    /* Translucent, but deliberately NOT blurred.

       Liquid Glass is a material for layers that FLOAT above content —
       navigation, sheets, controls. A dashboard card is content, and
       giving 27 of them a backdrop-filter each cost more than it looked
       worth: 42 blurred layers on this page scrolled at 7fps, against
       22fps with the card blur removed. The floating bar keeps the full
       material; the cards keep the surface, the hairline and the
       specular inset, which still read as glass over the page's own
       gradient.

       The blur has to be turned OFF explicitly: app.all.css gives every
       .card in the app a backdrop-filter, so merely leaving it out here
       changes nothing. (That app-wide rule is the larger cost — every
       page pays it — but it is not this file's to change.) */
    background: var(--ios26-surface-strong) !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    box-shadow: var(--ios26-inset), var(--ios26-shadow-1) !important;

    /* The card settles at --nz-card-fit and may NEVER pass --nz-card-max.
       min() of the two makes the rule structural: raise the working
       height all you like, the phone-portrait ceiling still wins.

       The fit matters as much as the ceiling. With only the 956px bound
       in place the timesheet widgets grew to 949 and 956, so one band of
       three widgets cost a whole screen — technically legal, useless as
       an overview. */
    display: flex;
    flex-direction: column;
    max-height: min(var(--nz-card-fit), var(--nz-card-max));
}

.nz-dash26 .card > .card-header {
    flex: 0 0 auto;
}

/* Everything after the title shrinks and scrolls on its own, but does NOT
   grow.

   `min-height: 0` is what lets it shrink — without it a flex child refuses
   to go below its content height and the cap does nothing.

   `flex-grow: 0` is what stops the opposite failure. A Chart.js canvas
   with `maintainAspectRatio: false` has no intrinsic height: it takes
   whatever its container offers. With a growing body that became a loop —
   body grows, canvas fills it, card grows — and the card came to rest
   against whatever the cap happened to be. The timesheet widgets measured
   956px when the ceiling was 956 and 560px when it was 560, with a 390px
   canvas that had declared 221px. Content decides the height; the cap is
   only a limit. */
.nz-dash26 .card > *:not(.card-header) {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* an inner scroller keeps the glass edge clean */
.nz-dash26 .card > *::-webkit-scrollbar {
    width: 6px;
}

.nz-dash26 .card > *::-webkit-scrollbar-thumb {
    border-radius: var(--ios26-pill);
    background: var(--ios26-hairline-strong);
}

.nz-dash26 .card > *::-webkit-scrollbar-track {
    background: transparent;
}

/* a widget that measured its own inner scroller in JS (perfect-scrollbar
   with an explicit setHeight) must not push the card past the cap */
.nz-dash26 .card [id$="-container"].ps {
    max-height: calc(var(--nz-card-fit) - 64px) !important;
}

/* the specular sheen: follows the pointer via --nz-mx/--nz-my, which
   assets/js/dashboard-ios26.js writes on the card */
.nz-dash26 .card::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    background: radial-gradient(360px 260px at var(--nz-mx, 50%) var(--nz-my, 0%),
                rgba(255, 255, 255, .30), transparent 70%);
    transition: opacity 0.4s var(--ios26-ease);
}

.nz-dash26 .card:hover::after {
    opacity: 1;
}

body.color-1E202D .nz-dash26 .card::after {
    background: radial-gradient(360px 260px at var(--nz-mx, 50%) var(--nz-my, 0%),
                rgba(255, 255, 255, .07), transparent 70%);
}

/* --- the widget title, on the iOS 26 type scale ------------------------ */

.nz-dash26 .card > .card-header {
    display: flex;
    align-items: center;
    gap: 9px;

    padding: 14px 18px 11px !important;
    border-bottom: 1px solid var(--ios26-hairline) !important;
    background: transparent !important;

    font-size: var(--nz-title);
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.012em;
    text-transform: none;
    color: var(--ios26-text) !important;
}

/* the header icon gets its own tinted plate, matching the KPI tiles */
.nz-dash26 .card > .card-header > i:first-child,
.nz-dash26 .card > .card-header > span:first-child > i:first-child {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    margin: 0 !important;
    border-radius: 8px;
    font-size: 13px !important;
    line-height: 1;
}

/* painted only once the card's own colour has been read — see above */
.nz-dash26 .card[data-nz-tint] > .card-header > i:first-child,
.nz-dash26 .card[data-nz-tint] > .card-header > span:first-child > i:first-child {
    background: var(--nz-tint-soft);
    color: var(--nz-tint);
}

/* an iOS 26 title is one line. Let it truncate rather than wrap: a widget
   whose header carries a control (the month stepper, a currency picker)
   was breaking "All timesheets" across two lines and shoving the card's
   whole content down with it. */
.nz-dash26 .card > .card-header {
    flex-wrap: nowrap;
    white-space: nowrap;
}

/* The title keeps a floor. A widget whose header carries a wide control
   (the timesheet month stepper is 210px) was squeezing its title to
   literally 0px wide at tablet widths — the card lost its name entirely,
   and the control still did not fit. The title shrinks to a readable
   minimum and then the CONTROL gives way instead. */
.nz-dash26 .card > .card-header > span:not(.dropdown):first-of-type,
.nz-dash26 .card > .card-header > .nz-card-title {
    flex: 1 1 auto;
    min-width: 4.5em;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* one title bar height across every widget, whether or not the header
   carries a control. Without this a month stepper made its card's title
   bar 60px while its neighbours sat at 51 — visible as a stepped row of
   card tops. */
.nz-dash26 .card > .card-header {
    min-height: 52px;
}

.nz-dash26 .card > .card-header .btn,
.nz-dash26 .card > .card-header .date-range-selector,
.nz-dash26 .card > .card-header input,
.nz-dash26 .card > .card-header select {
    max-height: 30px;
    padding-top: 2px;
    padding-bottom: 2px;
    font-size: 13px;
    line-height: 1.2;
}

/* A control in a title bar is a chip, not a button with an outline. The
   app's `.btn-default` is a white pill, which reads as the loudest thing
   on the card in the dark theme — brighter than the widget's own title. */
.nz-dash26 .card > .card-header .btn,
.nz-dash26 .card > .card-header .date-range-selector {
    border: 0 !important;
    border-radius: var(--ios26-pill) !important;
    background: var(--ios26-fill) !important;
    box-shadow: none !important;
    color: var(--ios26-text) !important;
}

.nz-dash26 .card > .card-header .btn:hover,
.nz-dash26 .card > .card-header .btn:focus,
.nz-dash26 .card > .card-header .date-range-selector:hover {
    background: var(--ios26-fill-hover) !important;
    color: var(--ios26-text) !important;
}

.nz-dash26 .card > .card-header .float-end,
.nz-dash26 .card > .card-header .dropdown {
    flex: 0 1 auto;
    min-width: 0;
    max-width: 62%;
    margin-left: auto;
    overflow: hidden;
    white-space: nowrap;
}

/* inside a shrunk control it is the label that gives, not the arrows */
.nz-dash26 .card > .card-header .float-end > span,
.nz-dash26 .card > .card-header .date-range-selector {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nz-dash26 .card > .card-body {
    padding: 16px 18px !important;
    background: transparent !important;
}

/* numbers inside widgets read as data, not as prose */
.nz-dash26 .card .strong,
.nz-dash26 .card h1,
.nz-dash26 .card h3,
.nz-dash26 .card h4 {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    letter-spacing: -0.018em;
}

.nz-dash26 .card h4.strong {
    font-size: 26px;
    font-weight: 700;
}

/* --- progress bars, as iOS capsules ----------------------------------- */

.nz-dash26 .progress {
    height: 6px;
    border-radius: var(--ios26-pill);
    background: var(--ios26-fill) !important;
    overflow: hidden;
}

.nz-dash26 .progress-bar {
    border-radius: var(--ios26-pill);
}

.nz-dash26 .widget-progress-bar {
    height: 6px;
}

/* The team-avatar summary under the timesheet chart grows a row per ~14
   people. At 27 members it made the card 554px naturally and dragged its
   two 250/384px neighbours up with it — one widget deciding a whole band.
   Two rows, then it scrolls: it is a strip, not a roster. */
.nz-dash26 .card .avatar-group {
    max-height: 76px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* the progression readout of the projects widget: value above a capsule */
.nz-dash26 .nz-progress-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 6px;
}

.nz-dash26 .nz-progress-label {
    font-size: 13px;
    color: var(--ios26-muted);

    /* "Progression" is a translated string and some languages are much
       longer; without a floor of zero it refuses to shrink and pushes the
       percentage out of the card instead. The figure is the part that must
       survive. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nz-dash26 .nz-progress-value {
    flex: 0 0 auto;
    padding-left: 10px;
    font-size: 15px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
    color: var(--ios26-text);
}

/* --- readable figures ------------------------------------------------
   The widgets colour their COUNTS to match the status beside them:
   "39 Not paid" was painted #FAC108, which is 1.64:1 against the card —
   a 14px number nobody can read. 71 of 322 text nodes on this dashboard
   failed WCAG AA that way in light mode.

   The fix is the iOS pattern, not a darker yellow: the dot, the bar and
   the chart segment carry the hue, and the number is a number. It also
   makes a column of counts comparable at a glance, which a column of
   different colours is not.

   These win over inline `style="color:…"`, which is why they need
   !important. */
.nz-dash26 .card .strong,
.nz-dash26 .card .fw-bold,
.nz-dash26 .card h1,
.nz-dash26 .card h3,
.nz-dash26 .card h4 {
    color: var(--ios26-text) !important;
}

/* ...but a coloured dot, bar or swatch is not text — leave it alone */
.nz-dash26 .card .color-tag,
.nz-dash26 .card .progress-bar,
.nz-dash26 .card .badge,
.nz-dash26 .card .widget-icon {
    color: inherit;
}

/* a chart with nothing in it, replaced by a sentence */
.nz-dash26 .card .nz-chart-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;

    min-height: 120px;
    padding: 24px 16px;
    text-align: center;

    border-radius: var(--ios26-radius);
    background: var(--ios26-fill);
    color: var(--ios26-muted);
    font-size: 13px;
    line-height: 1.45;
}

.nz-dash26 .card .nz-chart-empty i {
    font-size: 22px;
    opacity: 0.55;
}

/* --- status chips ----------------------------------------------------
   Two more places where hue was standing in for legibility:

   - a Bootstrap contextual badge paints the app's default grey text on a
     saturated fill; "Added" on bg-success measured 1.12:1.
   - the activity log marks a changed value with <ins>/<del>, which the
     app renders in flat teal — 2.64:1 at 14px.

   Both become the iOS chip instead: a soft tint of the hue behind text
   of the same hue at readable strength. The meaning survives, and so
   does the reader. */
.nz-dash26 .card .badge.bg-success,
.nz-dash26 .card ins {
    background: rgba(1, 179, 147, 0.16) !important;
    color: #0b6b59 !important;
}

.nz-dash26 .card .badge.bg-danger,
.nz-dash26 .card del {
    background: rgba(245, 50, 92, 0.14) !important;
    color: #b3123a !important;
}

.nz-dash26 .card .badge.bg-warning,
.nz-dash26 .card .badge.bg-orange {
    background: rgba(250, 193, 8, 0.22) !important;
    color: #7a5c00 !important;
}

.nz-dash26 .card .badge.bg-primary,
.nz-dash26 .card .badge.bg-info {
    background: rgba(102, 144, 244, 0.16) !important;
    color: #2f5fd0 !important;
}

.nz-dash26 .card .badge.bg-secondary {
    background: var(--ios26-fill) !important;
    color: var(--ios26-text) !important;
}

.nz-dash26 .card ins,
.nz-dash26 .card del {
    padding: 0 5px;
    border-radius: 5px;
}

.nz-dash26 .card ins {
    text-decoration: none;
}

body.color-1E202D .nz-dash26 .card .badge.bg-success,
body.color-1E202D .nz-dash26 .card ins {
    background: rgba(1, 179, 147, 0.20) !important;
    color: #57dcc0 !important;
}

body.color-1E202D .nz-dash26 .card .badge.bg-danger,
body.color-1E202D .nz-dash26 .card del {
    background: rgba(245, 50, 92, 0.20) !important;
    color: #ff92a9 !important;
}

body.color-1E202D .nz-dash26 .card .badge.bg-warning,
body.color-1E202D .nz-dash26 .card .badge.bg-orange {
    background: rgba(250, 193, 8, 0.20) !important;
    color: #f2c65c !important;
}

body.color-1E202D .nz-dash26 .card .badge.bg-primary,
body.color-1E202D .nz-dash26 .card .badge.bg-info {
    background: rgba(102, 144, 244, 0.22) !important;
    color: #a6bcff !important;
}

/* Bootstrap's contextual text colours are tuned for buttons and alerts,
   not for 14px figures on a translucent card: .text-primary measured
   3.03:1 and .text-success 4.48:1 on the Open Projects percentages.
   Same hues, readable strengths. */
.nz-dash26 .card .text-primary {
    color: #2f5fd0 !important;
}

.nz-dash26 .card .text-success {
    color: #0b6b59 !important;
}

.nz-dash26 .card .text-danger {
    color: #b3123a !important;
}

.nz-dash26 .card .text-warning {
    color: #7a5c00 !important;
}

.nz-dash26 .card .text-info {
    color: #1668a8 !important;
}

body.color-1E202D .nz-dash26 .card .text-primary {
    color: #a6bcff !important;
}

body.color-1E202D .nz-dash26 .card .text-success {
    color: #57dcc0 !important;
}

body.color-1E202D .nz-dash26 .card .text-danger {
    color: #ff92a9 !important;
}

body.color-1E202D .nz-dash26 .card .text-warning {
    color: #f2c65c !important;
}

body.color-1E202D .nz-dash26 .card .text-info {
    color: #85c6f5 !important;
}

/* an appTable's own chrome inside a dashboard card — the row count, the
   "Loading..." line — sits at 3.78:1 on the dark card */
/* apptable-glass.css reaches these with (0,4,3); this has to outrank it */
body.color-1E202D .nz-dash26 .card .dt-container table.dataTable td.dt-empty,
body.color-1E202D .nz-dash26 .card .dt-container .dt-info,
body.color-1E202D .nz-dash26 .card .dt-info,
body.color-1E202D .nz-dash26 .card .dataTables_info {
    color: var(--ios26-muted) !important;
}

/* --- the colour dots the list widgets use ----------------------------- */

.nz-dash26 .card .color-tag.wh10 {
    width: 9px;
    height: 9px;
    box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 14%, transparent);
}

/* the widgets lean on big fixed paddings (pt35, pt20) that were tuned for
   the old card height; inside the glass they read as dead space */
.nz-dash26 .card .pt35 {
    padding-top: 18px !important;
}

.nz-dash26 .card .pt20 {
    padding-top: 12px !important;
}

/* A link in a flex row needs a floor of zero or it refuses to shrink: a
   project named "Reconciliation_of_intercompany_balances_Q3" has no space
   to break at, so the title pushed its row open and was clipped at the card
   edge. This lives outside the touch query on purpose — iteration 28 fixed
   the tablet case and left the desktop one. */
.nz-dash26 .card .d-flex > a,
.nz-dash26 .card .d-flex > a.dark,
.nz-dash26 .card .d-flex > div > a.dark {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* `.text-truncate` only ellipsises the element's OWN text. Several widgets
   put the content in a child instead, which then slides straight through
   the truncation and out of the row. Make the utility mean what its name
   says for one level of nesting. */
.nz-dash26 .card .text-truncate > * {
    min-width: 0;
    max-width: 100%;
    /* NOT `overflow: hidden` here. An inline-block whose overflow is not
       visible takes its BOTTOM MARGIN EDGE as its baseline instead of its
       last line box, which pushed the count in "155 Overdue" 4px out of
       line with its label. The clipping is redundant anyway: the
       .text-truncate parent already hides what escapes it, and max-width
       is what stops the child forcing the row open. */
}

/* a chip is a label, not a paragraph */
.nz-dash26 .card .badge {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: bottom;
}

/* A string with nowhere to break — a project reference, a German compound,
   a pasted identifier — otherwise pushes its container open wherever it
   lands. Breaking mid-word is the lesser evil, and it applies only when
   there is no other opportunity. Rules that set `nowrap` (the card titles,
   the flex-row links) still win, because they forbid wrapping outright. */
.nz-dash26 .card {
    overflow-wrap: anywhere;
}

/* --- list rows inside widgets ----------------------------------------- */

.nz-dash26 .card .card-body a.text-default > div {
    border-radius: var(--ios26-radius-sm);
    transition: background 0.2s var(--ios26-ease);
}

.nz-dash26 .card .card-body a.text-default:hover > div {
    background: var(--ios26-fill);
}

/* ================================================ notice banners =====
   The first things on the dashboard are an unread-announcement alert and
   the "install this app" prompt, both Bootstrap `.alert-warning`: a flat
   yellow bar above a page of glass. The PWA one is rendered OUTSIDE
   #page-content, so it needs `:has()` rather than a descendant selector.
   ===================================================================== */

.nz-dash26 .alert,
body:has(.nz-dash26) .install-pwa-button {
    display: flex;
    align-items: center;
    gap: 10px;

    margin: 0 0 var(--nz-gap) !important;
    padding: 12px 16px !important;

    border: 1px solid var(--ios26-edge) !important;
    border-radius: var(--ios26-radius-lg) !important;
    background: var(--ios26-surface-strong) !important;
    box-shadow: var(--ios26-inset), var(--ios26-shadow-1) !important;

    color: var(--ios26-text) !important;
    font-size: 14px;
    line-height: 1.4;
}

/* the hue moves to the icon, the way it does on the widget cards */
.nz-dash26 .alert > i:first-child,
body:has(.nz-dash26) .install-pwa-button i:first-child {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 30px;
    height: 30px;
    margin: 0 !important;
    border-radius: 9px;
    background: rgba(250, 193, 8, 0.18);
    color: #7a5c00;
    font-size: 14px !important;
}

body.color-1E202D .nz-dash26 .alert > i:first-child,
body.color-1E202D:has(.nz-dash26) .install-pwa-button i:first-child {
    background: rgba(250, 193, 8, 0.20);
    color: #f2c65c;
}

.nz-dash26 .alert a,
body:has(.nz-dash26) .install-pwa-button a {
    color: var(--ios26-text) !important;
    font-weight: 500;
}

.nz-dash26 .alert .btn-close,
body:has(.nz-dash26) .install-pwa-button .btn-close {
    margin-left: auto;
    opacity: 0.5;
}

body:has(.nz-dash26) .install-pwa-button #install-pwa {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 !important;
}

/* ===================================================== empty state ====
   What a fresh dashboard shows. The app's own form rules ask for a large
   plain title, one line of help and the obvious next move; the old state
   was a sentence floating over a grey icon and no way forward. */
.nz-dash26 .nz-dash-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 6px;

    min-height: 420px;
    padding: 40px 24px;

    border: 1px solid var(--ios26-edge);
    border-radius: var(--ios26-radius-lg);
    background: var(--ios26-surface-strong);
    box-shadow: var(--ios26-inset), var(--ios26-shadow-1);
}

.nz-dash26 .nz-dash-empty-plate {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 84px;
    height: 84px;
    margin-bottom: 14px;
    border-radius: 22px;
    background: var(--ios26-accent-soft);
    color: var(--ios26-accent);
    font-size: 34px;
}

.nz-dash26 .nz-dash-empty h2 {
    margin: 0;
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--ios26-text);
}

.nz-dash26 .nz-dash-empty p {
    max-width: 44ch;
    margin: 0 0 18px;
    font-size: 14px;
    line-height: 1.5;
    color: var(--ios26-muted);
}

.nz-dash26 .nz-dash-empty .btn {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 0 18px;
    border-radius: var(--ios26-pill);
}

/* ====================================================== focus ring ====
   Every control here already got a focus ring — the browser's 1px default.
   That is the one piece of the interface that was not designed, and on a
   translucent card it is also the one that most needs to be: iOS uses a
   thick accent ring with an offset, and the accent token is themed, so it
   stays visible in dark mode where a near-black hairline does not.

   The offset is NEGATIVE inside a card on purpose: `.card` is
   `overflow: hidden`, so a ring drawn outside a full-width row would be
   clipped away exactly where it matters. */
.nz-dash26 :focus-visible {
    outline: 2px solid var(--ios26-accent);
    outline-offset: 2px;
    border-radius: var(--ios26-radius-sm);
}

.nz-dash26 .card :focus-visible {
    outline-offset: -2px;
}

/* the bar floats clear of everything, so its controls can breathe */
.nz-dash26 .nz-dash-bar :focus-visible {
    outline-offset: 3px;
}

/* a ring on a coloured chip needs its own separation from the fill */
.nz-dash26 .card .badge:focus-visible,
.nz-dash26 .card .btn:focus-visible {
    outline-offset: 1px;
    box-shadow: 0 0 0 4px var(--ios26-accent-ring);
}

/* The dark accent (#5b73e8) is chosen to sit calmly on a dark surface,
   which is the opposite of what a focus ring needs: it measured 2.7:1
   against the card, under the 3:1 WCAG asks of a non-text indicator.
   A brighter blue is used for the ring only. */
body.color-1E202D .nz-dash26 :focus-visible {
    outline-color: #8aa2ff;
}

/* ============================================================= dark === */

/* the same surface the light theme uses. This block used to hand back
   --ios26-surface (62%), which was right while the cards were blurred —
   without the blur behind it the page reads straight through, and dark
   cards ended up more transparent than light ones. */
body.color-1E202D .nz-dash26 .card,
body.color-1E202D .nz-dash26 .card.dashboard-icon-widget {
    background: var(--ios26-surface-strong) !important;
    border-color: var(--ios26-edge) !important;
}

body.color-1E202D .nz-dash26 .card > .card-header,
body.color-1E202D .nz-dash26 .card > .card-body {
    background: transparent !important;
    color: var(--ios26-text) !important;
}

/* Body text inside a dark card inherits the theme's mid grey, which sat at
   4.29:1 once the card surface went from 62% to 84% opacity — the invoice
   amounts, and anything else that just inherits. Elements with a colour of
   their own (.text-off, the status chips) keep it, so the hierarchy
   survives. */
body.color-1E202D .nz-dash26 .card,
body.color-1E202D .nz-dash26 .card .text-default,
/* `.dark` is a link colour, named for the ink rather than the theme — so
   in the dark theme it paints dark text on a dark card (4.29:1) */
body.color-1E202D .nz-dash26 .card .dark {
    color: var(--ios26-text) !important;
}

/* The list widgets only populate after their ajax round, so these came
   in under the radar: to-do rows, appTable cell links and the pager all
   inherit theme colours that measure 2.9-3.5:1 on the dark card. */
body.color-1E202D .nz-dash26 .card .todo-row,
body.color-1E202D .nz-dash26 .card .js-selection-id,
body.color-1E202D .nz-dash26 .card table.dataTable td,
body.color-1E202D .nz-dash26 .card table.dataTable td a {
    color: var(--ios26-text) !important;
}

body.color-1E202D .nz-dash26 .card .page-link,
body.color-1E202D .nz-dash26 .card .dt-paging .page-link {
    color: var(--ios26-muted) !important;
}

body.color-1E202D .nz-dash26 .card .page-item.active .page-link {
    color: #fff !important;
}

/* secondary text uses the theme's own grey, which lands at 4.29:1 on the
   card surface. --ios26-muted was picked for exactly these surfaces, and
   AA applies to secondary text too. */
body.color-1E202D .nz-dash26 .card .text-off {
    /* !important because color/1E202D.css is injected at RUNTIME and so
       comes last in the cascade — see .claude/rules/frontend-ui.md */
    color: var(--ios26-muted) !important;
}

body.color-1E202D .nz-dash26 .nz-dash-bar {
    background: var(--ios26-surface);
    border-color: var(--ios26-edge);
}

/* ================================================== accent tokens =====
   A card can only be tinted once assets/js/dashboard-ios26.js has READ a
   colour out of it (see findWidgetColor) — which works when the widget
   paints something, and not when it is a plain list of counts. These
   classes let a view DECLARE its accent instead, in the same two custom
   properties the JS writes, so a card, a section heading or a stat tile
   picks up its hue with no probe and no inline style.

   The values are the readable pairs already used by the chip and figure
   rules above: hue on the light theme, the brightened variant on dark.
   ===================================================================== */

.nz-dash26 .nz-tint-blue {
    --nz-tint: #2f5fd0;
    --nz-tint-soft: rgba(102, 144, 244, .16);
}

.nz-dash26 .nz-tint-green {
    --nz-tint: #0b6b59;
    --nz-tint-soft: rgba(1, 179, 147, .16);
}

.nz-dash26 .nz-tint-red {
    --nz-tint: #b3123a;
    --nz-tint-soft: rgba(245, 50, 92, .14);
}

.nz-dash26 .nz-tint-amber {
    --nz-tint: #7a5c00;
    --nz-tint-soft: rgba(250, 193, 8, .22);
}

.nz-dash26 .nz-tint-violet {
    --nz-tint: #5b3fd6;
    --nz-tint-soft: rgba(142, 124, 240, .16);
}

body.color-1E202D .nz-dash26 .nz-tint-blue {
    --nz-tint: #a6bcff;
}

body.color-1E202D .nz-dash26 .nz-tint-green {
    --nz-tint: #57dcc0;
}

body.color-1E202D .nz-dash26 .nz-tint-red {
    --nz-tint: #ff92a9;
}

body.color-1E202D .nz-dash26 .nz-tint-amber {
    --nz-tint: #f2c65c;
}

body.color-1E202D .nz-dash26 .nz-tint-violet {
    --nz-tint: #c3b5ff;
}

/* A declared accent is right from the FIRST paint — no probe to wait for,
   so the icon plate never flashes the app's solid #4466F2 default on the
   way to its real colour. The `[data-nz-tint]` rule above still applies
   once the JS has run; this one simply does not need it. */
.nz-dash26 .card[class*="nz-tint-"] .widget-icon {
    background: var(--nz-tint-soft) !important;
    color: var(--nz-tint) !important;
}

.nz-dash26 .card[class*="nz-tint-"] .widget-icon i {
    color: var(--nz-tint) !important;
}

/* A band whose widget count does not divide by its columns leaves a hole
   at the end of the last row, which reads as a widget that failed to
   load. Centring the leftovers under the row above closes it without
   resizing anything: every tile in a band keeps the same width, which is
   what makes their numbers comparable at a glance. A row that fills its
   12 columns exactly is unaffected. */
.nz-dash26 .dashboards-row.nz-band {
    justify-content: center;
}

/* ================================================ section headings ====
   The default dashboard is one continuous surface of widgets. A PAGE
   built out of the same material — the clients overview is the first —
   has to say what each band of widgets is FOR, or it reads as twelve
   unrelated numbers.

   iOS groups a settings list exactly this way: a heading and one line of
   help, in the page's own space rather than in a box of its own. The
   plate carries the band's accent so the eye can find the band again
   after scrolling.
   ===================================================================== */

.nz-dash26 .nz-dash-section {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 2px 2px 14px;
}

/* a band that follows another one needs air above it, not beside it */
.nz-dash26 .dashboards-row + .nz-dash-section {
    margin-top: 14px;
}

.nz-dash26 .nz-dash-section-plate {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;

    width: 34px;
    height: 34px;

    border: 1px solid var(--ios26-edge);
    border-radius: 11px;
    background: var(--nz-tint-soft);
    color: var(--nz-tint);
    box-shadow: var(--ios26-inset);

    font-size: 15px;
    line-height: 1;
}

.nz-dash26 .nz-dash-section-text {
    min-width: 0;
}

.nz-dash26 .nz-dash-section-title {
    display: block;
    margin: 0;
    font-size: 17px;
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: -0.014em;
    color: var(--ios26-text);
}

/* one line of help, and only one: it is here to be skimmed */
.nz-dash26 .nz-dash-section-sub {
    display: block;
    margin-top: 1px;
    font-size: 13px;
    line-height: 1.4;
    letter-spacing: -0.004em;
    color: var(--ios26-muted);
}

/* ===================================================== icon plates ====
   `.widget-icon` is the app's own 55px solid block, floated left. The KPI
   strip re-cuts it above; a stat tile and a widget title bar need the
   same plate at their own sizes, and they get the tinted repaint from the
   `.card[data-nz-tint] .widget-icon` rule either way.
   ===================================================================== */

.nz-dash26 .card > .card-header > .widget-icon,
.nz-dash26 .nz-stat-card .widget-icon {
    float: none !important;
    display: flex !important;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    padding: 0 !important;
    border: 1px solid var(--ios26-edge);
    box-shadow: var(--ios26-inset);
}

/* app.all.css sizes `.widget-icon .icon` at 2rem square and paints it
   white for the solid block; neither survives the plate */
.nz-dash26 .card > .card-header > .widget-icon i,
.nz-dash26 .nz-stat-card .widget-icon i {
    width: auto !important;
    height: auto !important;
    line-height: 1;
}

.nz-dash26 .card > .card-header > .widget-icon {
    width: 28px;
    height: 28px;
    border-radius: 9px;
}

.nz-dash26 .card > .card-header > .widget-icon i {
    font-size: 13px;
}

.nz-dash26 .nz-stat-card .widget-icon {
    width: 38px;
    height: 38px;
    border-radius: 12px;
}

.nz-dash26 .nz-stat-card .widget-icon i {
    font-size: 16px;
}

/* ======================================================= stat tile ====
   "How many of my clients are in this state" — a count, the share it is
   of the whole, and the capsule that shows it. The old markup floated the
   figure opposite a caption and left the card with no icon at all, so a
   band of five of them was five identical grey rectangles.

   Same anatomy as a KPI readout (plate, label, figure) plus the bar, so
   the two bands read as one family.
   ===================================================================== */

/* A stat tile has no chart in it, so — unlike the general card rule above,
   which must not let a canvas grow the card — its body may fill the tile
   and centre what it holds. Otherwise the content sits at the top and a
   tile stretched to its band's height carries a strip of dead space. */
.nz-dash26 .nz-stat-card > .card-body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    justify-content: center;
    gap: 13px;
}

.nz-dash26 .nz-stat-head {
    display: flex;
    align-items: center;
    gap: 12px;
}

.nz-dash26 .nz-stat-text {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
}

.nz-dash26 .nz-stat-title {
    font-size: 14px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: -0.008em;
    color: var(--ios26-text);

    /* a state name is one line — "Clients with partially paid invoices"
       wrapped to three in French and made its card 40px taller than the
       two beside it */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nz-dash26 .nz-stat-sub {
    margin-top: 2px;
    font-size: 12px;
    line-height: 1.35;
    color: var(--ios26-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* the figure is a readout, not a heading — a <span>, so it never lands in
   the heading outline the way the KPI tiles' <h1> did */
.nz-dash26 .nz-stat-figure {
    flex: 0 0 auto;
    font-size: 26px;
    font-weight: 700;
    line-height: 1.05;
    letter-spacing: -0.024em;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    color: var(--ios26-text);
}

.nz-dash26 a:hover .nz-stat-card {
    transform: translateY(-2px);
    box-shadow: var(--ios26-inset), var(--ios26-shadow-2) !important;
}

/* ===================================================== chart card =====
   A widget whose body is a time series: a headline readout, the plot, and
   an optional note under it. The plot needs a box with a HEIGHT of its
   own — a Chart.js canvas with `maintainAspectRatio: false` has no
   intrinsic height and takes whatever it is given, which is nothing
   inside a flex column that may not grow.
   ===================================================================== */

.nz-dash26 .nz-ca-head {
    display: flex;
    align-items: flex-end;
    gap: 14px;
    margin-bottom: 14px;
}

.nz-dash26 .nz-ca-head-main {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-width: 0;
}

.nz-dash26 .nz-ca-delta {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    text-align: right;
}

.nz-dash26 .nz-ca-delta-value {
    font-size: 17px;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -0.014em;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    color: var(--ios26-muted);
}

/* growth is the one number on this page that earns a colour */
.nz-dash26 .nz-ca-delta-value.is-up {
    color: #0b6b59;
}

body.color-1E202D .nz-dash26 .nz-ca-delta-value.is-up {
    color: #57dcc0;
}

.nz-dash26 .nz-chart-box {
    position: relative;
    height: 230px;
}

.nz-dash26 .nz-chart-note {
    margin: 12px 0 0;
    font-size: 12px;
    line-height: 1.45;
    color: var(--ios26-muted);
}

/* ================================================== list-of-states ====
   The projects / estimates / proposals widgets: three or four rows, each
   a client state and its count. The app's `.list-group-item` paints an
   opaque block (#252932 in the dark theme) which sat on the glass as a
   flat panel, and the rows were `<a>` wrapping `<li>` — invalid, and
   floated their counts with `.float-end`.

   Here they are the iOS grouped-list row: transparent, a hairline
   between, the hue on the leading icon and the count left as a number.
   ===================================================================== */

.nz-dash26 .card .nz-list {
    margin: 0;
}

.nz-dash26 .card .nz-list .list-group-item {
    padding: 0 !important;
    border: 0 !important;
    background: transparent !important;
}

.nz-dash26 .card .nz-list .list-group-item + .list-group-item {
    border-top: 1px solid var(--ios26-hairline) !important;
}

.nz-dash26 .card .nz-list-row {
    display: flex;
    align-items: center;
    gap: 11px;
    min-height: 46px;
    padding: 9px 18px;
    color: var(--ios26-text);
    transition: background 0.2s var(--ios26-ease);
}

.nz-dash26 .card .nz-list-row:hover,
.nz-dash26 .card .nz-list-row:focus {
    background: var(--ios26-fill);
    color: var(--ios26-text);
}

.nz-dash26 .card .nz-list-icon {
    flex: 0 0 auto;
    width: 18px;
    text-align: center;
    font-size: 15px;
}

.nz-dash26 .card .nz-list-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 14px;
    letter-spacing: -0.006em;
}

.nz-dash26 .card .nz-list-value {
    flex: 0 0 auto;
    font-size: 16px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
    letter-spacing: -0.014em;
    color: var(--ios26-text);
}

/* the list runs edge to edge inside the card, so the body gives up its
   own padding to it */
.nz-dash26 .card > .card-body.nz-list-body {
    padding: 4px 0 !important;
}

/* =========================================================== mobile === */

@media (max-width: 800px) {
    .nz-dash26 {
        --nz-gap: 14px;
        /* a phone also carries the bottom tab bar, so it needs a little
           more headroom than the shared clamp above allows */
        --nz-card-max: clamp(240px, calc(100dvh - 150px), 956px);
        --nz-card-fit: clamp(240px, calc(100dvh - 150px), 560px);
    }

    /* Bootstrap rows carry negative side margins to cancel the column
       gutters. Stacked full-width on a phone there are no gutters to
       cancel, so those margins are 6px of sideways page scroll and
       nothing else. */
    .nz-dash26 .row {
        --bs-gutter-x: 0;
        margin-left: 0;
        margin-right: 0;
    }

    .nz-dash26 .widget-container {
        padding-left: 0;
        padding-right: 0;
    }

    /* The brief's floating navigation layer matters MOST here, and this
       was the one place it did not float: `position: static` let the bar
       scroll away completely (measured 551px above the viewport), taking
       the dashboard's name, its switcher and its menu with it — and the
       adaptive tint with them, since nothing was on screen to tint.

       It stays a capsule and condenses harder than on desktop: 76px of a
       956px phone is a lot to spend on a title. */
    /* `position: sticky` sticks to the nearest ancestor that forms a
       scrollport. On a phone the WINDOW scrolls, but `.page-container` is
       `overflow-y: auto` and so still counts as one — a scrollport that
       never scrolls, which traps the bar inside it and lets it ride away
       with the content. Releasing that container hands the bar back to
       the viewport. Scoped with :has() because it is the app's element,
       and it is not scrolling on this breakpoint anyway. */
    body:has(.nz-dash26) .page-container {
        overflow: visible !important;
    }

    .nz-dash26 .nz-dash-bar {
        position: sticky;
        top: 6px;
        padding: 10px 14px;
    }

    .nz-dash26 .nz-dash-bar.is-condensed {
        padding: 5px 12px;
    }

    .nz-dash26 .nz-dash-bar.is-condensed .nz-dash-bar-title {
        font-size: 16px;
    }

    /* the tap targets below already carry their own 44px floor */
    .nz-dash26 .nz-dash-bar.is-condensed #dashboards-color-tags .clickable,
    .nz-dash26 .nz-dash-bar.is-condensed .dashboard-dropdown .dropdown-toggle {
        width: 38px;
        height: 38px;
    }

    /* ---------------------------------------- KPI strip, in the hand ---
       Full width, one per row, the figure marooned on the right: four
       tiles cost two screens before a single widget appears. Two up,
       stacked icon-over-figure, is the iOS widget shape and costs one
       third of the height. */
    .nz-dash26 .widget-container:has(.dashboard-icon-widget) {
        flex: 0 0 50%;
        max-width: 50%;
    }

    .nz-dash26 .widget-container:has(.dashboard-icon-widget):nth-child(odd) {
        padding-right: calc(var(--nz-gap) / 2);
    }

    .nz-dash26 .widget-container:has(.dashboard-icon-widget):nth-child(even) {
        padding-left: calc(var(--nz-gap) / 2);
    }

    .nz-dash26 .dashboard-icon-widget {
        max-height: none;
    }

    .nz-dash26 .dashboard-icon-widget .card-body {
        flex-direction: column;
        align-items: flex-start;
        gap: 10px;
        padding: 14px !important;
    }

    .nz-dash26 .dashboard-icon-widget .widget-icon {
        order: 0;
        width: 34px;
        height: 34px;
        border-radius: 10px;
    }

    .nz-dash26 .dashboard-icon-widget .widget-icon i {
        font-size: 15px;
    }

    .nz-dash26 .dashboard-icon-widget .widget-details {
        order: 1;
        width: 100%;
        text-align: left !important;
    }

    /* the figure has to survive "€351,754.57" in half a phone width */
    .nz-dash26 .dashboard-icon-widget .widget-details h1 {
        font-size: clamp(15px, 4.6vw, 20px);
    }

    .nz-dash26 .dashboard-icon-widget .widget-details span,
    .nz-dash26 .dashboard-icon-widget .widget-details .bg-transparent-white {
        font-size: 12px;
    }

}

/* the new page-level components, in the hand ------------------------- */

@media (max-width: 800px) {
    .nz-dash26 .nz-dash-section {
        gap: 10px;
        margin-bottom: 12px;
    }

    .nz-dash26 .nz-dash-section-plate {
        width: 30px;
        height: 30px;
        border-radius: 10px;
        font-size: 13px;
    }

    .nz-dash26 .nz-dash-section-title {
        font-size: 16px;
    }

    .nz-dash26 .nz-dash-section-sub {
        font-size: 12px;
    }

    /* a stat tile keeps its shape — plate, label, figure — it just loses
       the widths it does not have */
    .nz-dash26 .nz-stat-card .widget-icon {
        width: 34px;
        height: 34px;
    }

    .nz-dash26 .nz-stat-figure {
        font-size: 22px;
    }

    .nz-dash26 .card .nz-list-row {
        padding-left: 14px;
        padding-right: 14px;
    }

    /* a time series in the hand: shorter, and the headline gives way to
       the figures rather than to the caption beside them */
    .nz-dash26 .nz-chart-box {
        height: 190px;
    }

    .nz-dash26 .nz-ca-head {
        gap: 10px;
    }
}

/* ================================================= touch targets ====
   Keyed on the POINTER, not just the width. All of this used to live
   inside the <=800px block, so an iPad in landscape — a coarse pointer
   at 1024px — got mouse-sized controls: measured there, 154 of 227 were
   under 44pt, including the dashboard switcher at 26x26 and the invoice
   rows at 28px tall. Layout stays width-based (a tablet has room for
   three columns); only the hit areas follow the pointer.
   ===================================================================== */

@media (max-width: 800px), (pointer: coarse) {
    /* ------------------------------------------------- thumb targets ---
       iOS asks for 44x44pt minimum, and a dashboard is read one-handed.
       Everything below grows the TOUCH area; none of it moves the layout
       or changes a type size. */

    /* the dashboard switcher dots and the overflow menu in the bar */
    .nz-dash26 .nz-dash-bar #dashboards-color-tags .clickable,
    .nz-dash26 .nz-dash-bar .dashboard-dropdown .dropdown-toggle {
        width: 44px;
        height: 44px;
    }

    /* a widget's list rows — invoice states, ticket states, task statuses.
       They are full width already; they were just too short to hit. */
    .nz-dash26 .card a.text-default > div,
    .nz-dash26 .card a.white-link > div,
    .nz-dash26 .card .invoice-overview-widget-link > div {
        display: flex;
        align-items: center;
        /* 48, not the bare 44 minimum: a row sits directly against its
           neighbours, so an exact-minimum row leaves no margin for a
           thumb that lands slightly off */
        min-height: 48px;
        padding-top: 4px;
        padding-bottom: 4px;
    }

    /* Block-level rows — a project title beside its percentage, a task
       row in an appTable, a to-do item. These carry real height: an
       invisible hit area would not help, because the siblings painted
       after them (the percentage, the next row) sit on top of it and
       take the tap. */
    .nz-dash26 .card .d-flex > a,
    .nz-dash26 .card td a,
    .nz-dash26 .card li > a,
    .nz-dash26 .card a.dark {
        display: flex;
        align-items: center;
        min-height: 44px;
    }

    /* A link in a flex row is only as wide as its text — "CICD" came out
       33px across on a tablet even though its row was 44 tall. Let it take
       the free space so the row itself is the target; the figure beside it
       stays put at the end. */
    .nz-dash26 .card .d-flex > a,
    .nz-dash26 .card .d-flex > a.dark {
        flex: 1 1 auto;
        min-width: 0;
    }

    /* small isolated links with nothing painted over them — priority
       counters, the "see all" tails. Here a pseudo-element hit area is
       enough, and it leaves the text exactly where it is. */
    .nz-dash26 .card span > a:only-child {
        position: relative;
    }

    .nz-dash26 .card span > a:only-child::before {
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        width: 100%;
        height: 100%;
        min-width: 44px;
        min-height: 44px;
        transform: translate(-50%, -50%);
    }

    /* dropdown rows in a widget menu */
    .nz-dash26 .dropdown-item {
        min-height: 44px;
        display: flex;
        align-items: center;
    }

    /* icon-only controls: an appTable row action, a to-do checkbox, an
       avatar chip, a widget button. `:has(> i:only-child)` is what tells
       an icon BUTTON apart from a link inside a sentence — the inline
       links in the activity log must keep their place in the text. */
    .nz-dash26 .card .btn,
    .nz-dash26 .card button,
    .nz-dash26 .card .user-avatar,
    .nz-dash26 .card .action-option,
    .nz-dash26 .card input[type="checkbox"],
    .nz-dash26 .card a:has(> i:only-child),
    .nz-dash26 .card span:has(> i:only-child).clickable {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 44px;
        min-height: 44px;
    }

    /* the avatar image keeps its own size inside the bigger target */
    .nz-dash26 .card .user-avatar img {
        width: 30px;
        height: 30px;
    }

    /* These arrive with the ajax list widgets, which is why they escaped
       the sweep for so long: a to-do tick at 35x44 and pagination buttons
       at 30x44 clear the height and miss on width. The paging selectors
       need `.dt-container` to outrank apptable-glass.css. */
    .nz-dash26 .card .dt-container .dt-paging .page-link,
    .nz-dash26 .card .page-link,
    .nz-dash26 .card .update-todo-status-checkbox,
    .nz-dash26 .card a[class*="checkbox"] {
        display: inline-flex !important;
        align-items: center;
        justify-content: center;
        min-width: 44px !important;
        min-height: 44px !important;
    }

    /* The "..." menu on a widget header (currency pickers) is a bare div at
       39x21, and the task-priority counters are 18px-tall links in a row.
       Both were passing only because the old probe accepted a hit on an
       ANCESTOR; with that loosened rule removed they are what they always
       were — too small to hit. */
    .nz-dash26 .card > .card-header .dropdown-toggle,
    .nz-dash26 .card .dropdown-toggle.clickable {
        display: inline-flex !important;
        align-items: center;
        justify-content: center;
        min-width: 44px;
        min-height: 44px;
    }

    .nz-dash26 .card span[class*="me-"] > a,
    .nz-dash26 .card .d-flex > span > a {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 44px;
        min-height: 44px;
    }

    /* a text field is a tap target too — an appTable's search box at 34px
       is a miss waiting to happen */
    .nz-dash26 .card input.form-control,
    .nz-dash26 .card select.form-control,
    .nz-dash26 .card .dt-search input,
    .nz-dash26 .card .form-control-sm {
        min-height: 44px;
    }

    /* a 44-wide button is exactly on the line; a thumb aiming at its
       centre can still land a pixel outside it */
    .nz-dash26 .card .btn,
    .nz-dash26 .card button {
        min-width: 56px;
        padding-left: 12px;
        padding-right: 12px;
    }

    /* an appTable's sortable column header IS the target; the little
       order caret inside it is decoration */
    .nz-dash26 .card thead th {
        min-height: 44px;
        height: 44px;
    }
}


/* The OS-level "reduce transparency" setting is a request, not a taste.
   It also has to be answered honestly: --ios26-surface-strong is still
   78% alpha, so handing it back leaves the page translucent, only less
   so. These are opaque. */
@media (prefers-reduced-transparency: reduce) {
    /* `.card.dashboard-icon-widget` above is (0,3,0); a media query adds
       no specificity of its own, so these have to match it or the tiles
       keep their translucent surface. */
    .nz-dash26 .card,
    .nz-dash26 .card.dashboard-icon-widget,
    .nz-dash26 .nz-dash-bar {
        background: #ffffff !important;
        border-color: var(--ios26-hairline-strong) !important;
        -webkit-backdrop-filter: none !important;
        backdrop-filter: none !important;
    }

    body.color-1E202D .nz-dash26 .card,
    body.color-1E202D .nz-dash26 .card.dashboard-icon-widget,
    body.color-1E202D .nz-dash26 .nz-dash-bar {
        background: #2c303a !important;
    }

    .nz-dash26 .card::before,
    .nz-dash26 .card::after,
    .nz-dash26 .nz-dash-bar::before,
    .nz-dash26 .nz-dash-bar::after,
    .nz-dash26 .dashboard-icon-widget::before {
        display: none;
    }
}

/* ============================================================ print ===
   On screen a card that outgrows the cap scrolls inside itself. On paper
   nothing scrolls: the same `max-height` + `overflow: hidden` silently
   throws the rest of the list away — measured, all 27 cards were clipping.
   So print releases the cap, and drops the material (translucency and
   shadows cost ink and print grey).
   ===================================================================== */

@media print {
    /* The page lives inside a viewport-height scroll container, so the
       print came out as ONE A4 page with everything below the fold thrown
       away. `:has()` keeps this to pages that actually carry a dashboard,
       since these are the app's own containers and not this file's. */
    /* The app pins html/body to `overflow: hidden` so the inner container
       does the scrolling. A document that cannot scroll cannot paginate
       either: the print came out as 15 pages with everything after the
       first one blank. */
    html:has(.nz-dash26),
    body:has(.nz-dash26) {
        overflow: visible !important;
        height: auto !important;
    }

    body:has(.nz-dash26) .page-container,
    body:has(.nz-dash26) .scrollable-page,
    body:has(.nz-dash26) #left-menu-toggle-mask {
        height: auto !important;
        max-height: none !important;
        overflow: visible !important;
        margin-bottom: 0 !important;
    }

    /* navigation is not part of a printed report */
    body:has(.nz-dash26) .sidebar,
    body:has(.nz-dash26) #mobile-bottom-menu,
    /* the topbar is `position: fixed`, so it reprinted on all 8 pages */
    body:has(.nz-dash26) #default-navbar,
    body:has(.nz-dash26) .navbar.fixed-top,
    body:has(.nz-dash26) .install-pwa-message,
    body:has(.nz-dash26) #pre-loader {
        display: none !important;
    }

    .nz-dash26 {
        padding-bottom: 0;
    }

    /* nothing scrolls on paper, so nothing may be capped */
    .nz-dash26 .card,
    .nz-dash26 .card.dashboard-icon-widget {
        max-height: none !important;
        overflow: visible !important;
        background: #fff !important;
        border: 1px solid #d5d7dd !important;
        box-shadow: none !important;
        -webkit-backdrop-filter: none !important;
        backdrop-filter: none !important;
        break-inside: avoid;
        page-break-inside: avoid;
    }

    /* On screen the card is a fixed-height box whose body shrinks and
       scrolls. On paper it has to be the opposite: the body sizes to its
       content and the card grows with it. Without this the band holding
       the list widgets stayed 996px tall around 4,774px of content, and
       the overflow simply painted over whatever came next. */
    .nz-dash26 .card > *:not(.card-header) {
        flex: 0 0 auto !important;
        min-height: auto !important;
        overflow: visible !important;
    }

    .nz-dash26 .dashboards-row {
        align-items: flex-start !important;
    }

    /* `height: auto` alone is not enough: these are flex items with
       `flex: 1 1 auto`, so the column still hands them a share of a fixed
       height instead of letting them size to content. */
    .nz-dash26 .widget-container > .card,
    .nz-dash26 .widget-container > div,
    .nz-dash26 .widget-container > a,
    .nz-dash26 .widget-container > div > .card,
    .nz-dash26 .widget-container > a > .card {
        flex: 0 0 auto !important;
        height: auto !important;
    }

    /* the on-screen rule is `.nz-dash26 .card [id$="-container"].ps`, at
       (0,4,0) — an !important at (0,3,0) loses to it, so this has to be
       at least as specific */
    .nz-dash26 .card [id$="-container"].ps,
    .nz-dash26 .card .ps.ps--active-y,
    .nz-dash26 .card .table-responsive {
        max-height: none !important;
        height: auto !important;
        overflow: visible !important;
    }

    /* the widgets' own inner scrollers carry an inline height from
       initScrollbar(), which a max-height cannot beat */
    .nz-dash26 .card .ps,
    .nz-dash26 .card [id$="-container"] {
        height: auto !important;
        max-height: none !important;
        overflow: visible !important;
    }

    /* the floating layer stops floating */
    .nz-dash26 .nz-dash-bar {
        position: static !important;
        background: none !important;
        border: 0 !important;
        box-shadow: none !important;
        -webkit-backdrop-filter: none !important;
        backdrop-filter: none !important;
        padding: 0 0 12px !important;
    }

    .nz-dash26 .nz-dash-bar #dashboards-color-tags,
    .nz-dash26 .nz-dash-bar .dashboard-dropdown {
        display: none !important;
    }

    /* the tint lens, the specular sheen and the KPI wash are all ink */
    .nz-dash26 .card::before,
    .nz-dash26 .card::after,
    .nz-dash26 .nz-dash-bar::before,
    .nz-dash26 .nz-dash-bar::after,
    .nz-dash26 .dashboard-icon-widget::before {
        display: none !important;
    }

    /* a dark theme must not print dark */
    body.color-1E202D .nz-dash26,
    body.color-1E202D .nz-dash26 .card,
    body.color-1E202D .nz-dash26 .card .text-default,
    body.color-1E202D .nz-dash26 .card .dark,
    body.color-1E202D .nz-dash26 .nz-dash-bar-title {
        color: #111 !important;
        background: #fff !important;
    }

    body.color-1E202D .nz-dash26 .card .text-off {
        color: #555 !important;
    }
}

@media (prefers-reduced-motion: reduce) {
    .nz-dash26 .nz-dash-bar,
    .nz-dash26 .card,
    .nz-dash26 .card::after,
    .nz-dash26 .dashboard-icon-widget {
        transition: none !important;
    }
}
