/*
 * JobTrack design system — "Console".
 *
 * Visual language: a modern, faintly futuristic operations console for tracked labour and cost.
 * A light working canvas of layered white cards over a warm neutral paper ground, driven by a single
 * burnt-orange accent, sits beneath a deep, softly-lit command header. Data is the hero:
 * money and identifiers render in tabular monospace, and the cost metric is treated as an
 * instrument read-out rather than body text.
 *
 * These tokens override Bootstrap 5.3's CSS custom-property layer (--bs-*) rather than forking
 * Bootstrap's Sass, so the pinned vendor CSS in wwwroot/lib/bootstrap stays untouched and
 * upgradeable. The page markup composes a small vocabulary of primitives styled here — the
 * console header, record/stat cards, the metric read-out, the action toolbar, ledger tables,
 * status pills, and calm single-column forms — so the look lives in one place.
 */
/*
 * Self-hosted display face — "Mulish" (SIL OFL), the open geometric-humanist twin of Avenir Next.
 * Avenir Next itself is a Monotype commercial face bundled by macOS/iOS for system use only and may
 * not be redistributed, so it is never shipped here — the --jt-font-display stack simply lists it
 * first, letting Apple platforms render it natively while every other platform (and CI) falls back
 * to this pinned Mulish for an identical voice. Files pinned via libman.json (wwwroot/lib/mulish),
 * latin subset only; weights kept to those the display scale actually uses (see --jt-font-display).
 */
@font-face {
    font-family: "Mulish";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("../lib/mulish/files/mulish-latin-400-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Mulish";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("../lib/mulish/files/mulish-latin-600-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Mulish";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("../lib/mulish/files/mulish-latin-700-normal.woff2") format("woff2");
}

@font-face {
    font-family: "Mulish";
    font-style: normal;
    font-weight: 800;
    font-display: swap;
    src: url("../lib/mulish/files/mulish-latin-800-normal.woff2") format("woff2");
}

:root {
    color-scheme: light;

    /* Typography — a disciplined system-font scale; no webfonts. */
    --bs-font-sans-serif: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
    --bs-font-monospace: "SFMono-Regular", "JetBrains Mono", Consolas, "Liberation Mono", Menlo, monospace;
    /* Display face for titles and the brand — a rounded, youthful voice with real character where
       the platform offers it (Apple's rounded system face, Quicksand if installed), degrading to the
       body sans elsewhere. Keeps the no-webfont rule while giving headings a distinct, modern feel. */
    /* Display face — Avenir Next where the OS bundles it (macOS/iOS); the self-hosted OFL twin
       "Mulish" guarantees the same geometric-humanist voice everywhere else (Linux/Windows/CI); the
       rounded system stack stays a last-resort fallback while the webfont loads (font-display: swap). */
    --jt-font-display: "Avenir Next", "Mulish", ui-rounded, "SF Pro Rounded", "Hiragino Maru Gothic ProN", "Segoe UI", system-ui, sans-serif;
    --bs-body-font-size: 0.9375rem;
    --bs-body-line-height: 1.6;
    /* Sub-body sizes reused verbatim across unrelated components (table secondary columns, pill/tag
       labels, form micro-copy, table headers) — named here so every call site says what step of the
       scale it's on instead of repeating the same literal. Not a full scale: only sizes that were
       already exact duplicates before this was added. */
    --jt-font-size-sm: 0.8125rem;
    --jt-font-size-xs: 0.7rem;
    --jt-font-size-xxs: 0.6875rem;
    /* Title/heading step of the same scale — h1/h2 read these rather than a literal. Deliberately
       breakpoint-independent: type is one size everywhere from the tablet scale up, so widening the
       viewport never shrinks it (see the xxl density block below, which tightens whitespace only). */
    --jt-font-size-title: 2.15rem;
    --jt-font-size-heading: 1.45rem;

    /* Colour — burnt orange over warm neutral paper. */
    --jt-ink: #241c14;
    --jt-ink-soft: #3d3226;
    --jt-muted: #6a5c4b;
    /* Kept dark enough to clear WCAG AA (4.5:1) as small 11px label text on white surfaces — the
       <dt> record labels, metric sub-text, and breadcrumb separators all use it and are axe-scanned. */
    --jt-faint: #7c6c57;

    /* Bright-orange accent ramp. -600 is the text/link/fill workhorse — pushed as bright as WCAG AA
       allows on white (4.7:1, verified) for pop; -500/-400 are the vivid fills used behind white
       text, on the dark console, or in gradients/glows; -700 is hover/active. */
    --jt-orange-50: #fff4ec;
    --jt-orange-100: #ffe3d0;
    --jt-orange-200: #ffc59b;
    --jt-orange-400: #ff8a3d;
    --jt-orange-500: #f9640f;
    --jt-orange-600: #cf4409;
    --jt-orange-700: #b23808;
    /* Secondary accent — bright amber-gold for the metric baseline, section ticks, and the user dot. */
    --jt-gold-400: #fbbf24;

    --jt-surface: #ffffff;
    --jt-surface-2: #faf6f1;
    --jt-canvas: #f1ece5;
    --jt-line: #e7ded2;
    --jt-line-strong: #d6cabb;

    /* Structural slate — a second, cool accent leg reserved for hierarchy scaffolding: node-kind
       chips now, and (Phase B) the multi-level tree's trunk/branch rails and nested-set span bars.
       Keeps the warm burnt-orange reserved for cost and action, so structure never competes with it.
       Text-bearing weights (-600) are AA-verified on their chip backgrounds; lighter slates are
       non-text rails/fills only. */
    --jt-slate-600: #47586a;
    --jt-slate-500: #5f7285;
    --jt-slate-300: #a9b6c2;
    --jt-slate-100: #e4e9ee;
    --jt-slate-50: #f1f4f6;

    /* Command header — warm espresso-black with a burnt-orange underlight. */
    --jt-console-900: #1a1109;
    --jt-console-800: #241812;
    --jt-console-line: #45321f;
    --jt-console-text: #e2d5c4;

    /* Status */
    /* Container blue — the folder glyphs in the Browse tree, and nothing else. Deliberately not a
       status or accent colour: it is the one hue in the palette that means "this holds things",
       borrowed from the file-manager convention the tree rows imitate. Warmed slightly off a pure
       azure so it sits with the warm neutral ground rather than fighting it. */
    --jt-folder-600: #2d64b8;
    --jt-folder-400: #6d9bdd;

    --jt-green-700: #16704a;
    --jt-green-100: #e2f3ea;
    --jt-amber-700: #8a5606;
    --jt-amber-100: #fbeed3;
    --jt-red-700: #b3261e;
    --jt-red-600: #d32f2f;
    --jt-red-100: #fbe6e5;

    --bs-primary: var(--jt-orange-600);
    --bs-primary-rgb: 194, 65, 12;
    --bs-link-color: var(--jt-orange-600);
    --bs-link-hover-color: var(--jt-orange-700);
    --bs-body-color: var(--jt-ink);
    --bs-body-bg: var(--jt-canvas);
    --bs-secondary-color: var(--jt-muted);
    --bs-border-color: var(--jt-line);
    --bs-emphasis-color: var(--jt-ink);
    /* Bootstrap's default code colour is a pink (#d63384) that clears AA on white but not on a tinted
       alert -- which is where the app's one <code> actually lives (the one-time token secret, on amber).
       Machine text takes the same ink as the rest of the palette; the mono face already distinguishes it. */
    --bs-code-color: var(--jt-ink);

    /* Spacing scale (multiples of 0.25rem, matching Bootstrap's spacer unit). */
    --jt-space-1: 0.25rem;
    --jt-space-2: 0.5rem;
    --jt-space-3: 0.875rem;
    --jt-space-4: 1.25rem;
    --jt-space-5: 1.75rem;
    --jt-space-6: 2.5rem;

    /* Every floating disclosure (backdate/Start-for panels): above `.app-header`'s sticky 1030 so a
       panel opened near the top of the page is never tucked behind it, below Bootstrap's own modal
       (1055) in case a future dialog needs to sit above one of these. */
    --jt-z-popover: 1040;

    /* One layer of tree nesting, in the Browse subtree's description column. Sized so a rail and
       its elbow read as a distinct step at a glance without pushing a depth-5 row (the hard depth
       cap, JobSubtreeLimits.HardMaxDepth) off the right of a narrow viewport. */
    --jt-tree-indent: 1.5rem;

    /* A node's kind glyph (root/branch/leaf) and anything meant to read at the same visual weight
       beside it: the tree row's blocked flag and the icon-only status pills. Kept as one variable
       so the three never drift apart again. */
    --jt-icon-size-kind: 1.75rem;

    /* The same root/branch/leaf glyph, sized for sitting inline beside other text or standing alone
       in a table cell (.jt-kind-icon) rather than leading a tree row -- too small a target for a
       stepped hierarchy, but the right weight next to a line of prose or a cost figure. */
    --jt-icon-size-kind-inline: 1rem;

    /* Content measures — the widths that keep line length and field length readable. Every max-width in
       this stylesheet references one of these; a bare rem literal there is a magic number. */
    --jt-measure-page: 70rem;
    --jt-measure-prose: 52rem;
    --jt-measure-panel: 44rem;
    --jt-measure-field: 40rem;
    --jt-measure-auth: 26rem;

    /* WCAG 2.2 SC 2.5.8 Target Size (Minimum), AA: 24 CSS px. A floor, not a size -- it exists so a
       control sized in rem cannot fall under the normative minimum when the root scales down. */
    --jt-target-min: 24px;

    /* Shape — generously rounded for a younger, more dynamic feel. */
    --bs-border-radius: 0.875rem;
    --bs-border-radius-sm: 0.625rem;
    --jt-radius-lg: 1.375rem;

    /* Elevation — soft, layered, warm-tinted shadows. */
    --jt-shadow-sm: 0 1px 2px rgb(40 28 16 / 7%), 0 1px 3px rgb(40 28 16 / 6%);
    --jt-shadow-md: 0 6px 16px -6px rgb(40 28 16 / 14%), 0 2px 6px -2px rgb(40 28 16 / 9%);
    --jt-shadow-lg: 0 18px 40px -12px rgb(40 28 16 / 24%);
    --jt-glow: 0 0 0 1px rgb(207 68 9 / 12%), 0 10px 30px -10px rgb(249 100 15 / 48%);

    /* Focus ring — a bright-orange ring, visible on light chrome and the dark console. */
    --jt-focus-ring: 0 0 0 0.1875rem rgb(249 100 15 / 50%);

    --jt-transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

@media (prefers-reduced-motion: reduce) {
    :root {
        --jt-transition: 0ms;
    }
}

html {
    font-size: 14px;
    position: relative;
    min-height: 100%;
}

@media (min-width: 768px) {
    html {
        font-size: 16px;
    }
}

/* Desktop density — from xxl (1400px) up only; tablet and standard desktop (768–1399) keep the
   plain 16px scale exercised in the end-to-end viewport matrix, phones the 14px base above.

   Density here is *whitespace only* — gaps, padding, radii, line-height. This block deliberately
   does NOT touch the root font-size or any font-size token: a wider viewport must never render
   text smaller than a narrower one. It used to drop `html` to 14px, which shrank every rem-based
   size on the page at once (the visible symptom: type snapped smaller exactly as Browse's
   xxl-only Position column appeared between Cost and Actions).

   Because the root stays at 16px, every length below is the old 14px-root value rescaled by
   14/16 — the tightened spacing is preserved at its previous computed pixel size. */
@media (min-width: 1400px) {
    :root {
        --bs-body-line-height: 1.5;
        --jt-space-3: 0.65rem;
        --jt-space-4: 0.875rem;
        --jt-space-5: 1.2rem;
        --jt-space-6: 1.75rem;
        --bs-border-radius: 0.65rem;
        --bs-border-radius-sm: 0.4375rem;
        --jt-radius-lg: 1rem;
    }

    h1 {
        margin-bottom: var(--jt-space-3);
    }

    h3,
    h4 {
        margin-top: var(--jt-space-4);
    }

    .app-header .navbar {
        padding-top: var(--jt-space-2);
        padding-bottom: var(--jt-space-2);
    }

    .jt-brand-mark {
        width: 1.5rem;
        height: 1.5rem;
    }

    .btn {
        --bs-btn-padding-y: 0.325rem;
        --bs-btn-padding-x: 0.75rem;
    }

    dl {
        row-gap: var(--jt-space-2);
        padding: var(--jt-space-3);
    }
}

body {
    /* dvh, with vh first for anything that does not know the unit: on a mobile browser 100vh is the
       viewport with the address bar *retracted*, so it leaves ~60-100px of phantom scroll on every page
       that would otherwise fit. dvh tracks the chrome as it collapses. */
    min-height: 100vh;
    min-height: 100dvh;
    background: radial-gradient(1200px 600px at 78% -12%, rgb(249 100 15 / 10%), transparent 60%),
    var(--jt-canvas);
    -webkit-font-smoothing: antialiased;
}

/* Content column: capped for line-length discipline on the forms-and-ledgers pages, but wide
   enough for the dashboard card grids. */
.container {
    max-width: var(--jt-measure-page);
}

/* Removes the ~300ms tap delay and stops the double-tap-to-zoom gesture from firing on interactive
   elements. `manipulation` keeps panning and *pinch*-zoom, which is the gesture WCAG 1.4.4 requires and
   which the viewport meta deliberately leaves enabled -- only the double-tap shortcut goes, and only on
   controls, where a stray double-tap is a mis-zoom rather than an intent to read closer. */
a, button, [role="button"], input, select, textarea, .form-control, .form-select, .btn {
    touch-action: manipulation;
}

/* ---- Type scale --------------------------------------------------------------------------- */

h1,
h2,
h3,
h4 {
    color: var(--jt-ink);
    font-weight: 700;
    text-wrap: balance;
    /* Break an over-long unbreakable token (a user name, email, or identifier interpolated into a
       heading) rather than letting it force horizontal overflow — WCAG 1.4.10 reflow must hold at
       320 CSS px whatever the display face's metrics. */
    overflow-wrap: break-word;
}

h1 {
    margin: var(--jt-space-2) 0 var(--jt-space-4);
    font-family: var(--jt-font-display);
    font-size: var(--jt-font-size-title);
    font-weight: 800;
    letter-spacing: -0.035em;
    line-height: 1.05;
}

h2 {
    margin: var(--jt-space-4) 0 var(--jt-space-3);
    font-family: var(--jt-font-display);
    font-size: var(--jt-font-size-heading);
    font-weight: 750;
    letter-spacing: -0.025em;
}

/* h3/h4 mark subsections — a quiet uppercase console label with a short accent tick, not a
   full heading weight, so they read as section breaks rather than competing with h1/h2. */
h3,
h4 {
    display: flex;
    align-items: center;
    gap: var(--jt-space-2);
    margin: var(--jt-space-4) 0 var(--jt-space-3);
    font-size: var(--jt-font-size-sm);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--jt-muted);
}

h3::before,
h4::before {
    content: "";
    width: 0.75rem;
    height: 2px;
    border-radius: 2px;
    background: linear-gradient(90deg, var(--jt-orange-600), var(--jt-gold-400));
}

h4 {
    font-size: 0.75rem;
    margin-top: var(--jt-space-4);
}

p {
    max-width: var(--jt-measure-prose);
}

/* An opaque machine string -- a personal access token, a correlation id, a hash -- has no space to
   break at, so left alone it sets its own container's min-content width and pushes the page wider than
   the viewport. That is a WCAG 1.4.10 Reflow failure whose severity depends on where the random
   characters happen to fall, which makes it exactly the kind of defect that reproduces intermittently.
   `anywhere` rather than `break-word`: it lets the browser break mid-token only when nothing else
   fits, and unlike `word-break: break-all` it leaves ordinary prose in `code` alone. */
code {
    overflow-wrap: anywhere;
}

/* Bootstrap 5.3 bakes the base `a` colour in as a literal hex at compile time rather than
   referencing --bs-link-color, so the :root override never reaches plain anchors. This explicit
   rule is what actually applies the token (originally caught by the axe-core colour-contrast
   scan on a footer link). -600 is only verified at 4.7:1 against white `.jt-card`
   surfaces; unwrapped body content (a `<table>` sitting straight on `--jt-canvas`, not inside a
   card) drops to 3.98:1, short of WCAG AA's 4.5:1 -- so the resting link colour uses -700
   (~5.16:1 against canvas, higher still against white) instead, with hover/focus (not itself
   subject to the AA text-contrast rule) keeping the brighter -600. */
a {
    color: var(--jt-orange-700);
    text-decoration-color: rgb(178 56 8 / 35%);
    text-underline-offset: 0.15em;
    transition: color var(--jt-transition);
}

a:hover,
a:focus-visible {
    color: var(--jt-orange-600);
    text-decoration-color: currentColor;
}

/* Focus visibility: a consistent burnt-orange ring on every interactive control. */
a:focus-visible,
button:focus-visible,
.btn:focus-visible,
.form-control:focus-visible,
.form-select:focus-visible,
.form-check-input:focus-visible,
summary:focus-visible {
    outline: none;
    box-shadow: var(--jt-focus-ring);
    border-radius: var(--bs-border-radius-sm);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--jt-orange-500);
    box-shadow: var(--jt-focus-ring);
}

/* ---- Command header ----------------------------------------------------------------------- */

.app-header {
    position: sticky;
    top: 0;
    z-index: 1030;
    background: radial-gradient(120% 180% at 88% -40%, rgb(249 100 15 / 42%), transparent 55%),
    linear-gradient(180deg, var(--jt-console-800), var(--jt-console-900));
    border-bottom: 1px solid var(--jt-console-line);
    box-shadow: 0 1px 0 0 rgb(249 100 15 / 68%), 0 10px 30px -18px rgb(0 0 0 / 80%);
}

.app-header .navbar {
    padding-top: var(--jt-space-3);
    padding-bottom: var(--jt-space-3);
}

.app-header .navbar-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.625rem;
    font-family: var(--jt-font-display);
    font-weight: 800;
    letter-spacing: -0.01em;
    font-size: 1.15rem;
    color: #fff;
}

/* Brand emblem — a geometric bright-orange chip with a luminous edge. */
.jt-brand-mark {
    display: grid;
    place-items: center;
    width: 2rem;
    height: 2rem;
    border-radius: 0.625rem;
    background: linear-gradient(140deg, var(--jt-orange-400), var(--jt-orange-600));
    box-shadow: inset 0 1px 0 rgb(255 255 255 / 40%), 0 4px 14px -2px rgb(249 100 15 / 65%);
    color: #fff;
}

/* GitHub link — trailing item in the right-hand nav, so it lands at the far right of the titlebar. */
.jt-github-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 0.625rem;
    color: var(--jt-console-text);
    transition: color var(--jt-transition), background-color var(--jt-transition);
}

.jt-github-link:hover,
.jt-github-link:focus-visible {
    color: #fff;
    background-color: rgb(255 255 255 / 8%);
}

.jt-github-link-glyph {
    width: 1.15rem;
    height: 1.15rem;
    fill: currentcolor;
}

.app-header .nav-link {
    position: relative;
    color: var(--jt-console-text);
    font-weight: 500;
    padding-left: 0.9rem;
    padding-right: 0.9rem;
    transition: color var(--jt-transition);
}

.app-header .nav-link:hover,
.app-header .nav-link:focus-visible {
    color: #fff;
}

/* Active-page underlight on the nav. */
.app-header .nav-link.active {
    color: #fff;
}

.app-header .nav-link.active::after {
    content: "";
    position: absolute;
    left: 0.9rem;
    right: 0.9rem;
    bottom: -0.35rem;
    height: 2px;
    border-radius: 2px;
    background: linear-gradient(90deg, var(--jt-orange-400), var(--jt-gold-400));
}

.app-header .navbar-text,
.app-header .jt-user {
    color: var(--jt-console-text);
}

.app-header .jt-user {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
}

.app-header .jt-user::before {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--jt-gold-400);
    box-shadow: 0 0 0 3px rgb(245 158 11 / 25%);
}

/* Sign-out / link buttons in the header read as quiet links, not filled buttons. */
.app-header .btn-link {
    color: var(--jt-console-text);
    text-decoration: none;
    font-weight: 500;
    padding: 0.5rem 0.9rem;
}

.app-header .btn-link:hover {
    color: #fff;
}

.app-header .navbar-toggler {
    border-color: var(--jt-console-line);
    color: var(--jt-console-text);
}

/* Draw the hamburger with three CSS bars rather than an SVG data URI — robust and crisp on the
   dark console header. */
.app-header .navbar-toggler-icon {
    background-image: linear-gradient(#fff, #fff),
    linear-gradient(#fff, #fff),
    linear-gradient(#fff, #fff);
    background-size: 100% 2px;
    background-position: center top 4px, center center, center bottom 4px;
    background-repeat: no-repeat;
}

/* ---- Main shell --------------------------------------------------------------------------- */

main[role="main"] {
    padding-top: var(--jt-space-4);
    padding-bottom: var(--jt-space-5);
    animation: jt-rise var(--jt-transition);
}

/* Transform-only entrance — deliberately NOT an opacity fade. A mid-flight opacity < 1 makes every
   descendant's text semi-transparent, which the end-to-end axe scan captures as a colour-contrast
   failure (it snapshots before the animation settles). Translating without fading avoids that
   while still giving a subtle rise. */
@keyframes jt-rise {
    from {
        transform: translateY(6px);
    }

    to {
        transform: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    main[role="main"] {
        animation: none;
    }
}

/* ---- Block-component vertical rhythm ------------------------------------------------------- */

/* Every block-level component owns its own gap to the next thing, here, on the --jt-space-* scale —
   never as an `mb-*` in the markup. Two mechanisms for one decision is what made the rhythm
   unpredictable: `.jt-form-card` spaced itself while `.jt-card` did not, `<dl>` declared a margin
   here *and* an `mb-4` at both call sites (the utility silently won, at Bootstrap's off-scale fixed
   1.5rem), `.jt-toolbar` shipped four different answers across sixteen uses, and `.jt-notice`
   declared nothing at all — so a blocked job's notice butted straight against the pill below it.
   The tokens are also breakpoint-scaled where Bootstrap's utilities are fixed rem, so a utility
   opts that element out of the whole responsive scale.
   A markup utility is now only for a deliberate per-instance deviation (`mb-0` to cancel), which
   BlockComponentSpacingArchitectureTests allows while banning the rest.
   The gap belongs *between* siblings, so a component that ends its container drops it rather than
   padding the container's own bottom out. */
.jt-card,
.jt-notice,
.jt-toolbar,
.jt-form-card,
.jt-table-block,
.jt-list,
.jt-empty,
.jt-page-head,
.jt-lede,
.jt-context,
fieldset,
dl {
    margin-bottom: var(--jt-space-4);
}

/* "Ends its container" has to mean *visibly* ends it: Razor appends the anti-forgery hidden input after
   the last real child of every POST form, so a plain `:last-child` would miss the action row that ends a
   form card and leave a trailing gap above the card's own padding. Read as: no following sibling other
   than a hidden input. Subsumes the true last-child case, so it is the only suppression needed. */
.jt-card:not(:has(~ *:not([type="hidden"]))),
.jt-notice:not(:has(~ *:not([type="hidden"]))),
.jt-toolbar:not(:has(~ *:not([type="hidden"]))),
.jt-form-card:not(:has(~ *:not([type="hidden"]))),
.jt-table-block:not(:has(~ *:not([type="hidden"]))),
.jt-list:not(:has(~ *:not([type="hidden"]))),
.jt-empty:not(:has(~ *:not([type="hidden"]))),
.jt-page-head:not(:has(~ *:not([type="hidden"]))),
.jt-lede:not(:has(~ *:not([type="hidden"]))),
.jt-context:not(:has(~ *:not([type="hidden"]))),
fieldset:not(:has(~ *:not([type="hidden"]))),
dl:not(:has(~ *:not([type="hidden"]))) {
    margin-bottom: 0;
}

/* ---- Cards -------------------------------------------------------------------------------- */

.jt-card {
    background: var(--jt-surface);
    border: 1px solid var(--jt-line);
    border-radius: var(--jt-radius-lg);
    padding: var(--jt-space-4);
    box-shadow: var(--jt-shadow-sm);
}

/* Metric read-out — the signature element. A large tabular number under a console eyebrow,
   with a hairline orange→gold baseline. Used for cost and headline figures. Multi-card layout
   comes from Bootstrap's row/row-cols-* grid on the markup, not from custom CSS here. */
.jt-subtree-head h2 {
    margin: 0;
}

.jt-metric {
    position: relative;
    overflow: hidden;
    padding: var(--jt-space-4);
    background: linear-gradient(180deg, var(--jt-surface), var(--jt-surface-2));
    border: 1px solid var(--jt-line);
    border-radius: var(--jt-radius-lg);
    box-shadow: var(--jt-shadow-sm);
}

.jt-metric::after {
    content: "";
    position: absolute;
    left: var(--jt-space-4);
    bottom: 0;
    width: 3rem;
    height: 3px;
    border-radius: 3px;
    background: linear-gradient(90deg, var(--jt-orange-600), var(--jt-gold-400));
}

.jt-metric-label {
    display: block;
    font-family: var(--bs-font-monospace);
    font-size: var(--jt-font-size-xxs);
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--jt-muted);
    margin-bottom: var(--jt-space-2);
}

.jt-metric-value {
    display: block;
    font-size: 2.35rem;
    font-weight: 800;
    line-height: 1.02;
    letter-spacing: -0.04em;
    color: var(--jt-ink);
    font-variant-numeric: tabular-nums;
}

.jt-metric-value.jt-amount {
    font-family: var(--bs-font-monospace);
    letter-spacing: -0.02em;
}

.jt-metric-sub {
    display: block;
    margin-top: var(--jt-space-1);
    font-size: var(--jt-font-size-sm);
    color: var(--jt-faint);
}

/* ---- Record cards (<dl>) ------------------------------------------------------------------ */

/* Every detail view renders a <dl> of one record's fields. Styled as a spec card: a label/value grid
   on a white surface with a burnt-orange spine. Layout is entirely Bootstrap's grid on the markup, at
   two levels: the <dl> is a `row row-cols-1 row-cols-md-2`, so fields stack on a phone and pair up from
   md; each field's wrapper is itself a `col row`, with its `dt`/`dd` as `col-12 col-sm-4`/`col-12
   col-sm-8` -- so the label sits above its value on a phone and beside it from sm. The wrapper carries
   both classes rather than nesting another <div>, because HTML5's <dl> content model (and axe's
   definition-list rule) require the div inside a <dl> to contain the dt/dd pair directly. */
dl {
    row-gap: var(--jt-space-3);
    /* Drops the UA's top margin only — the gap below comes from the block-component rule above, so a
       shorthand here would silently overwrite it. */
    margin-top: 0;
    padding: var(--jt-space-4) var(--jt-space-4) var(--jt-space-4) calc(var(--jt-space-4) + 3px);
    max-width: var(--jt-measure-panel);
    background: var(--jt-surface);
    border: 1px solid var(--jt-line);
    border-left: 3px solid var(--jt-orange-600);
    border-radius: var(--jt-radius-lg);
    box-shadow: var(--jt-shadow-sm);
}

/* The <dl> carries Bootstrap's `.row` for its label/value grid, and `.row` sets a negative inline
   margin — pulling the card's spine half a gutter left of every sibling block on the page. Restore
   the flush edge the base rule already intends so
   the record card lines up with the toolbar, list cards, and table below it. */
dl.row {
    margin-inline: 0;
}

/* Browse's record card runs full-bleed (two field columns on wide, one on phones, via the markup's
   row-cols; write-up spans both). Lift the shared 44rem cap so the section fills the content column,
   and zero the base row-gap so Bootstrap's `.g-2` gutter is the sole source of vertical rhythm (the
   two stacking gaps were the excess space). */
.jt-record--full {
    max-width: none;
    row-gap: 0;
}

/* Deliberately *not* `white-space: nowrap`: a label is a grid column that may be narrower than its own
   text, and nowrap made every label a hard floor under its column -- an over-long one overran the box
   and printed across its own value ("ACKNOWLEDGED" over its timestamp on a phone). Wrapping is the
   whole point of giving the label a real column. */
dt {
    font-size: var(--jt-font-size-xxs);
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--jt-faint);
    align-self: center;
}

dd {
    margin: 0;
    align-self: center;
    font-variant-numeric: tabular-nums;
    color: var(--jt-ink);
}

/* A link that qualifies the value it follows rather than being a value of its own (Browse's cost
   figure and its concurrent-work "Info"). A size down from the field value so the figure keeps the
   reading weight, and the aside reads as a footnote to it. */
.jt-value-aside {
    font-size: var(--jt-font-size-sm);
}

/* ---- Action toolbar ----------------------------------------------------------------------- */

/* Pages expose a row of navigation/action buttons, grouped into a wrapping toolbar (Bootstrap's
   d-flex flex-wrap gap-2 align-items-center on the markup) so they read as a control cluster
   rather than a stray paragraph of links. This class is a marker for `.jt-toolbar form` below. */
.jt-toolbar form {
    margin: 0;
}

/* Separates lead actions (Work/Delete on Browse) from the rest of the toolbar cluster. */
.jt-toolbar-divider {
    align-self: stretch;
    width: 1px;
    background: var(--jt-line-strong);
    margin-inline: var(--jt-space-1);
}

/* ---- Forms -------------------------------------------------------------------------------- */

/* The readable-width cap belongs to the controls, not to `<form>`. As an element rule it applied to
   every form on the site -- including the ones that wrap a toolbar, a single button, a filter grid or a
   table -- so five separate `max-width: none` escapes existed only to undo it (`.jt-toolbar form`,
   `form.inline-form`, `.jt-filter-form`, `.jt-backdate-form`, `.jt-auth form`), and a new wide form was
   silently capped until someone noticed and added a sixth. A text input, select or textarea is the thing
   whose line length actually matters, and capping it directly needs no escapes: a control inside a grid
   column or a compact panel is already narrower than the cap, so the rule simply never binds there.
   `.form-group` is a Bootstrap 4 holdover the markup still uses; it is styled here rather than renamed
   at every call site. */
.form-control,
.form-select,
textarea {
    max-width: var(--jt-measure-field);
}

form.inline-form {
    display: inline;
    margin: 0;
}

/* A framed card for the primary form on a page (login, create, edit…). */
.jt-form-card {
    padding: var(--jt-space-4);
    background: var(--jt-surface);
    border: 1px solid var(--jt-line);
    border-radius: var(--jt-radius-lg);
    box-shadow: var(--jt-shadow-md);
}

.form-group {
    margin-bottom: var(--jt-space-3);
}

/* The grid's own `g-3` gutter already spaces the fields both between columns and between wrapped
   rows -- `.form-group`'s own margin-bottom would stack on top of that gutter, doubling the gap
   once the fields wrap to one column on a narrow viewport. */
.jt-filter-form .form-group {
    margin-bottom: 0;
}

.form-group label,
.form-label {
    display: block;
    margin-bottom: var(--jt-space-1);
    font-size: var(--jt-font-size-sm);
    font-weight: 600;
    color: var(--jt-ink-soft);
}

.form-control,
.form-select {
    padding: 0.5rem 0.75rem;
    background-color: var(--jt-surface);
    border: 1px solid var(--jt-line-strong);
    border-radius: var(--bs-border-radius-sm);
    color: var(--jt-ink);
    transition: border-color var(--jt-transition), box-shadow var(--jt-transition);
}

.form-control::placeholder {
    color: var(--jt-faint);
}

/* One of the documented custom-breakpoint exceptions (CLAUDE.md): Bootstrap has no utility for
   "font-size floor below sm", and the thing being corrected is a consequence of the root-scale
   rule above, which is itself custom.

   Below the tablet breakpoint the root font-size drops to 14px, which would leave
   .form-control/.form-select at a 14px computed size (Bootstrap's font-size: 1rem) — under
   iOS Safari's 16px auto-zoom threshold, so focusing a field yanks the whole page in. Pinch-zoom
   is available to undo it (the viewport meta no longer disables zoom — WCAG 1.4.4), but making the
   reader fight the browser on every field is still the wrong default: pin form fields to the 16px
   floor on phones specifically rather than letting them track the root scale. */
@media (max-width: 767.98px) {
    .form-control,
    .form-select {
        font-size: 16px;
    }
}

fieldset {
    padding: var(--jt-space-3) var(--jt-space-4) var(--jt-space-2);
    /* White (not surface-2): the bright-orange <legend> needs a pure-white backdrop to clear WCAG
       AA at 11px — on the off-white surface-2 it dips to 4.35:1. */
    background: var(--jt-surface);
    border: 1px solid var(--jt-line);
    border-radius: var(--bs-border-radius);
}

fieldset legend {
    width: auto;
    padding: 0 var(--jt-space-2);
    margin-bottom: 0;
    font-size: var(--jt-font-size-xxs);
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--jt-orange-600);
}

.text-danger,
.field-validation-error {
    display: inline-block;
    margin-top: var(--jt-space-1);
    color: var(--jt-red-700) !important;
    font-size: var(--jt-font-size-sm);
}

.field-validation-valid {
    display: none;
}

/* ---- Buttons ------------------------------------------------------------------------------ */

.btn {
    --bs-btn-padding-y: 0.5rem;
    --bs-btn-padding-x: 1.1rem;
    font-weight: 600;
    letter-spacing: 0.005em;
    border-radius: var(--bs-border-radius-sm);
    transition: transform var(--jt-transition), background-color var(--jt-transition),
    border-color var(--jt-transition), color var(--jt-transition), box-shadow var(--jt-transition);
}

.btn:active {
    transform: translateY(1px);
}

/* Bootstrap 5.3 compiles .btn-primary's own --bs-btn-* custom properties to literal hex values at
   build time rather than referencing --bs-primary (the same issue documented for `a`), so the
   :root override never reaches the button. Set them explicitly. */
.btn-primary {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--jt-orange-600);
    --bs-btn-border-color: var(--jt-orange-600);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--jt-orange-700);
    --bs-btn-hover-border-color: var(--jt-orange-700);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--jt-orange-700);
    --bs-btn-active-border-color: var(--jt-orange-700);
    --bs-btn-disabled-bg: var(--jt-orange-400);
    --bs-btn-disabled-border-color: var(--jt-orange-400);
    background-image: linear-gradient(180deg, rgb(255 255 255 / 16%), rgb(255 255 255 / 0%));
    box-shadow: 0 1px 0 rgb(255 255 255 / 25%) inset, 0 6px 18px -8px rgb(249 100 15 / 75%);
}

.btn-primary:hover {
    box-shadow: 0 1px 0 rgb(255 255 255 / 25%) inset, 0 12px 26px -8px rgb(249 100 15 / 90%);
}

.btn-secondary {
    --bs-btn-color: var(--jt-ink-soft);
    --bs-btn-bg: var(--jt-surface);
    --bs-btn-border-color: var(--jt-line-strong);
    --bs-btn-hover-color: var(--jt-orange-700);
    --bs-btn-hover-bg: var(--jt-orange-50);
    --bs-btn-hover-border-color: var(--jt-orange-200);
    --bs-btn-active-color: var(--jt-orange-700);
    --bs-btn-active-bg: var(--jt-orange-100);
    --bs-btn-active-border-color: var(--jt-orange-200);
    box-shadow: var(--jt-shadow-sm);
}

.btn-danger {
    --bs-btn-bg: var(--jt-red-700);
    --bs-btn-border-color: var(--jt-red-700);
    --bs-btn-hover-bg: #93201a;
    --bs-btn-hover-border-color: #93201a;
}

/* ---- Alerts ------------------------------------------------------------------------------- */

/* Layout (icon-beside-message) comes from Bootstrap's d-flex align-items-start gap-2 on each
   call site's markup. */
.alert {
    max-width: var(--jt-measure-prose);
    border: 1px solid transparent;
    border-left-width: 3px;
    border-radius: var(--bs-border-radius-sm);
    box-shadow: var(--jt-shadow-sm);
}

.alert::before {
    font-weight: 700;
    line-height: 1.4;
}

.alert-danger {
    --bs-alert-color: var(--jt-red-700);
    --bs-alert-bg: var(--jt-red-100);
    --bs-alert-border-color: rgb(179 38 30 / 30%);
}

.alert-danger::before {
    content: "!";
    color: var(--jt-red-700);
}

.alert-success {
    --bs-alert-color: var(--jt-green-700);
    --bs-alert-bg: var(--jt-green-100);
    --bs-alert-border-color: rgb(23 115 74 / 30%);
}

/* The one-time secret on /Account/PersonalAccessTokens is the only warning alert in the app, and it was
   rendering in raw Bootstrap amber -- off-palette, and outside the token set every other surface is
   contrast-checked against. Same documented amber pair as the status pills. */
.alert-warning {
    --bs-alert-color: var(--jt-amber-700);
    --bs-alert-bg: var(--jt-amber-100);
    --bs-alert-border-color: rgb(138 86 6 / 30%);
}

.alert-success::before {
    content: "\2713";
    color: var(--jt-green-700);
}

/* ---- Status pills ------------------------------------------------------------------------- */

.status-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--jt-space-2);
    padding: var(--jt-space-2) var(--jt-space-3);
    border-radius: 2rem;
    font-weight: 600;
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    border: 1px solid transparent;
}

/* The default lead is a plain dot, for pills that report something other than readiness ("Active
   since…"). A readiness pill replaces it with a drawn sign — see `.status-pill-glyph`. */
.status-pill::before {
    content: "";
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 0 0 0 3px rgb(255 255 255 / 45%);
}

.status-pill-glyph {
    flex: 0 0 auto;
    width: 0.9375rem;
    height: 0.9375rem;
    fill: currentcolor;
}

/* A drawn sign replaces the dot rather than sitting next to it. */
.status-pill:has(> .status-pill-glyph)::before {
    content: none;
}

/* Icon-only: the sign alone, its name carried by a visually-hidden span. For a marker repeated
   once per row or list item, where a word of state per row would cost more width than the fact is
   worth — the record card keeps the worded pill. Padding is trimmed below `--jt-space-1` so the
   pill's own box (glyph + padding + border) lands at the same 24px the body text's line-height
   already resolves to at every breakpoint -- the exact height a marker-free `.jt-list > li` row
   takes, so a row with this pill never grows taller than one without it. */
.status-pill--icon {
    padding: 0.1875rem;
    gap: 0;
}

/* Icon-only pills carry no text to size against, so the glyph is sized against the inline kind icon
   (`--jt-icon-size-kind-inline`, `.jt-kind-icon-glyph`) that sits in the same row -- not the larger
   `--jt-icon-size-kind` used by the Browse subtree's own tree row, which this class never appears
   in. Matching that inline scale keeps every row of a jt-list (empty "None.", satisfied/blocking,
   and plain rows with no marker at all) the same height instead of the marker rows growing taller. */
.status-pill--icon .status-pill-glyph {
    width: var(--jt-icon-size-kind-inline);
    height: var(--jt-icon-size-kind-inline);
}

/* Icon-plus-value, no noun: the row-level "Active since" pill, reduced to its stopwatch and a
   compact timestamp. Never wraps — a two-line pill in one row of a table drags every other row's
   height with it, and the content is short enough by construction that there is nothing to gain
   from letting it break. */
.status-pill--compact {
    padding: var(--jt-space-1) var(--jt-space-2);
    font-size: var(--jt-font-size-xxs);
    white-space: nowrap;
}

.status-pill-ready {
    background-color: var(--jt-green-100);
    color: var(--jt-green-700);
    border-color: rgb(23 115 74 / 25%);
}

/* Active session count/since is operational state, not the stop/go readiness vocabulary (browse-
   sessions plan §2.4) -- amber, not the green .status-pill-ready an active leaf used to borrow,
   keeps "someone is clocked in right now" visually distinct from "this may proceed." */
.status-pill-active {
    background-color: var(--jt-amber-100);
    color: var(--jt-amber-700);
    border-color: rgb(138 86 6 / 25%);
}

/* Blocked is red, not the amber it used to be: the pill now carries a raised-palm stop sign, and a
   red hand on an amber ground reads as two states at once. Red is otherwise this palette's error
   colour, but a blocked job is the nearest thing to a stop the domain has, and `.jt-notice--error`
   is a separate component that never sits beside a status pill. */
.status-pill-blocked {
    background-color: var(--jt-red-100);
    color: var(--jt-red-700);
    border-color: rgb(179 38 30 / 25%);
}

/* Closed is neutral, not a stop/go signal: a leaf that is done (terminal achievement) or archived can
   accept no new session, but that is a settled, unalarming end-state — slate keeps it distinct from
   the amber "someone is working now", the red "blocked", and the green "may proceed". */
.status-pill-closed {
    background-color: var(--jt-slate-100);
    color: var(--jt-slate-600);
    border-color: rgb(71 88 106 / 25%);
}

/* Paused is In Progress with nobody clocked on — open work at rest. It borrows the amber ink of
   .status-pill-active (this is the same "someone is working this job" thread of state) but sits on
   the neutral ground rather than the amber one, so "started, nobody on it" never reads as "running
   right now" at a glance, nor as the settled slate-on-slate "Closed". Not red or otherwise alarming:
   ADR 0045 makes zero active sessions a valid state from InProgress, and Pause job produces it. */
.status-pill-paused {
    background-color: var(--jt-slate-50);
    color: var(--jt-amber-700);
    border-color: rgb(138 86 6 / 35%);
}

/* ---- Node-kind icon (Root / Branch / Leaf) -------------------------------------------------- */

/* The structural counterpart to .status-pill: names a node's derived hierarchy classification
   (ADR 0035) wherever it appears outside the Browse subtree's own tree row -- the Requires/
   RequiredBy lists, a node's own detail panel, a flat search-results table. Reuses the same glyphs
   and colour-by-kind rule as `.jt-tree-icon` (`--root`/`--branch`/`--leaf`, below) so a leaf reads
   as the same green shape everywhere in the app, just smaller here (`--jt-icon-size-kind-inline`)
   since this sits inline with other text rather than leading a tree row. `_KindIcon.cshtml` renders
   the label as visible text when there is room, or visually-hidden-only in a table cell -- either
   way the glyph itself is `aria-hidden`, so the fact is always carried by text for assistive tech. */
.jt-kind-icon {
    display: inline-flex;
    align-items: center;
    gap: var(--jt-space-1);
}

.jt-kind-icon-glyph {
    flex: 0 0 auto;
    width: var(--jt-icon-size-kind-inline);
    height: var(--jt-icon-size-kind-inline);
}

.jt-kind-icon-label {
    font-family: var(--bs-font-monospace);
    font-size: var(--jt-font-size-xs);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--jt-muted);
}

/* Bootstrap's `.visually-hidden` is permanent and has no responsive variant, so a label that is hidden
   only below a breakpoint has to be written here. Stated once, as the hidden state scoped to below lg --
   not as Bootstrap's hidden state applied unconditionally and then reversed by a second rule, which is
   the same decision made twice and needs `!important` to win the second time. `d-none d-lg-inline` is
   not the alternative: `display: none` takes the text out of the accessibility tree, and the point of
   this label is that it stays readable to a screen reader at every width.
   Below lg, not md: the Requires/Depends-on-this-job cards go side by side from md (`row-cols-md-2`), so
   revealing at md would bring the label back at the exact width where each card has least room. */
@media (max-width: 991.98px) {
    .jt-kind-icon-label--collapsible {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
}

/* ---- Achievement icon (Waiting / In progress / Success / Closed unfinished) ----------------- */

/* The state counterpart to `.jt-kind-icon`: names a leaf's recorded achievement wherever a job
   appears -- the Browse children/subtree tables, the Awaiting-progress list, a node's own detail
   panel. Same structure as the kind icon (inline flex, glyph + optional label, glyph always
   `aria-hidden` so the fact is carried by text for assistive tech), so the two sit together in a
   row without fighting.

   Colour carries the state and the glyph confirms it -- never colour alone, since these appear as
   icon-only markers in table cells. Amber for in-progress rather than green: green already means an
   open work session on the "Active since" pill, and a leaf can be In Progress with nobody working
   on it right now. Red is not used at all here -- it is reserved for blocked (`.status-pill-blocked`),
   which is an orthogonal fact a job carries *alongside* its achievement, not one of these values. */
.jt-achievement-icon {
    display: inline-flex;
    align-items: center;
    gap: var(--jt-space-1);
}

.jt-achievement-icon-glyph {
    flex: 0 0 auto;
    width: var(--jt-icon-size-kind-inline);
    height: var(--jt-icon-size-kind-inline);
    fill: currentcolor;
}

.jt-achievement-icon-label {
    font-size: var(--jt-font-size-sm);
    font-weight: 600;
}

.jt-achievement-icon--waiting {
    color: var(--jt-slate-500);
}

.jt-achievement-icon--in-progress {
    color: var(--jt-amber-700);
}

.jt-achievement-icon--success {
    color: var(--jt-green-700);
}

/* Warm dim, against Waiting's cool slate: the two neutrals must not be mistaken for each other, and
   a terminal state should recede rather than compete with the rows still needing attention. */
.jt-achievement-icon--closed {
    color: var(--jt-muted);
}

/* ---- Multi-level Browse tree (ADR 0039) --------------------------------------------------- */

/* The subtree reads like a file manager's list view: indentation for the level, an └ elbow drawn
   into it for the step down from the parent, and a kind icon naming what the row is (opened
   container / closed container / terminal leaf). Depth is therefore stated three ways — position,
   connector, glyph — none of which a screen reader can see, so each row also carries a
   visually-hidden "Level N, <kind>." label; the guide and the icon are both `aria-hidden`.

   The guide is absolutely positioned so its rails span the full row height and meet the rails of
   the rows above and below, making an unbroken line down each ancestor level. That needs the cell
   to be the containing block and to reserve the indent as padding — hence the per-depth padding
   rules below, bounded by JobSubtreeLimits.HardMaxDepth. Rails, elbows, and icons sit in the slate
   leg (the reserved hierarchy-scaffolding colour) and are non-text decoration, so no contrast floor
   applies to them; the row's own link carries the text contrast. */
/* Top-aligned, outranking `.table tbody td`'s middle: a row is as tall as its work controls, and a
   name floating in the middle of that band would break the stepped silhouette the indent draws.
   The elbow below is anchored to the same top edge, so the two stay locked together. */
.table tbody td.jt-tree-cell {
    vertical-align: top;
}

.jt-tree-cell {
    position: relative;

    vertical-align: top;
}

/* Two flex items — the icon, then everything textual — so a wrapped name aligns under itself
   rather than running back under the icon (the hanging indent a file manager's list view has),
   and the "more below" marker stays inline with the name's last word instead of being flexed to
   the far edge of the block. */
.jt-tree-label {
    display: flex;
    align-items: baseline;
    gap: var(--jt-space-2);
}

/* Clamp the whole textual run, rather than making the link itself an atomic inline box. The link's
   text can then wrap normally and every trailing marker flows directly after its final glyph; an
   atomic two-line link reserves the width of its longest line and leaves a conspicuous hole before
   a marker following a shorter final line. `nowrap` governs only the loose whitespace between the
   children -- `.jt-preserve-whitespace` still lets the link's own text wrap. */
.jt-tree-text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
}

.jt-tree-cell[data-jt-depth="1"] {
    padding-left: calc(1 * var(--jt-tree-indent));
}

.jt-tree-cell[data-jt-depth="2"] {
    padding-left: calc(2 * var(--jt-tree-indent));
}

.jt-tree-cell[data-jt-depth="3"] {
    padding-left: calc(3 * var(--jt-tree-indent));
}

.jt-tree-cell[data-jt-depth="4"] {
    padding-left: calc(4 * var(--jt-tree-indent));
}

.jt-tree-cell[data-jt-depth="5"] {
    padding-left: calc(5 * var(--jt-tree-indent));
}

.jt-tree-guide {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    display: flex;
}

.jt-tree-rail,
.jt-tree-elbow {
    position: relative;
    flex: 0 0 auto;
    width: var(--jt-tree-indent);
}

/* A full-height vertical rail — marks an ancestor level this row sits beneath. */
.jt-tree-rail::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 0;
    bottom: 0;
    border-left: 1px solid var(--jt-slate-100);
}

/* The └ elbow at the row's own level: stem down from the parent, rounded corner, arm out to the
   icon. Its arm stops on the first text line's centre — the cell's top padding plus half a line —
   rather than at the row's vertical midpoint, so it still points at the icon on a row whose name
   wrapped to two or three lines. */
.jt-tree-elbow::before {
    content: "";
    position: absolute;
    left: 50%;
    right: 0;
    top: 0;
    height: calc(0.75rem + 0.6rem);
    border-left: 1px solid var(--jt-slate-300);
    border-bottom: 1px solid var(--jt-slate-300);
    border-bottom-left-radius: 0.375rem;
}

/* ---- Tree row icons ------------------------------------------------------------------------ */

/* Defined once per page and drawn by <use>; never rendered itself. */
.jt-icon-sprite {
    display: none;
}

.jt-tree-icon {
    flex: 0 0 auto;
    width: var(--jt-icon-size-kind);
    height: var(--jt-icon-size-kind);

    /* Sits on the first line, optically centred on it, and stays there when the name wraps. The
       glyph is taller than the line box, so it is pulled up by the overhang rather than pushed
       down as a shorter one would be. */
    align-self: flex-start;
    margin-top: -0.15rem;
}

/* Containers are blue and leaves green, taking the metaphor literally so structure and terminal
   work separate at a glance. Neither is a state: both are glyphs beside a name, and the row's
   status pill remains the only thing that reports state. `fill` is set on the host <svg> because
   it is inherited, and inheritance is the only way paint reaches a <use> clone — which is also why
   the two-tone plates are an `opacity` on the back path rather than a second colour. */
.jt-tree-icon--root,
.jt-tree-icon--branch {
    fill: var(--jt-folder-600);
    color: var(--jt-folder-600);
}

.jt-tree-icon--leaf {
    fill: var(--jt-green-700);
    color: var(--jt-green-700);
}

/* An in-row state flag beside the name (currently: archived). Smaller than the kind glyph, muted,
   and always paired with visually-hidden text, since it carries a fact rather than decoration. */
.jt-tree-flag {
    width: 0.9375rem;
    height: 0.9375rem;
    margin-left: var(--jt-space-1);
    vertical-align: -0.15em;
    fill: var(--jt-muted);
    color: var(--jt-muted);
}

/* ---- Icon-only row actions (Start, Backdate) ------------------------------------------------- */

/* In a table row the action cell repeats once per row, so Start and its Backdate trigger are small
   square glyphs rather than labelled controls stacking a paragraph of text down every row. Each
   one's name is carried by visually-hidden text (and a `title` for a pointer tooltip), never by the
   glyph alone, and the whole square is the hit target so it stays comfortably tappable. Standalone
   page toolbars keep labelled buttons — an icon earns its keep by repetition, and a page's single
   primary action has none. */
.jt-icon-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 1.75rem is 24.5px against the phone's 14px root -- half a pixel of margin on an AA criterion. The
       max() makes the floor explicit rather than incidental, so a future root-size change cannot quietly
       take these under it. */
    width: max(1.75rem, var(--jt-target-min));
    height: max(1.75rem, var(--jt-target-min));
    padding: 0;
    border: 1px solid var(--jt-line);
    border-radius: 0.625rem;
    background: var(--jt-surface);
    color: var(--jt-muted);
    cursor: pointer;
    list-style: none;
    transition: color var(--jt-transition), border-color var(--jt-transition);
}

.jt-icon-button:hover,
.jt-backdate-trigger[aria-expanded="true"],
.jt-start-for-disclosure[open] > summary {
    color: var(--jt-orange-600);
    border-color: var(--jt-orange-600);
}

.jt-start-for-disclosure > summary::-webkit-details-marker {
    display: none;
}

/* The labelled variant (standalone toolbars) is a .btn, which carries no marker suppression of its
   own; the icon variant gets `list-style: none` from .jt-icon-button. Suppress the default disclosure
   triangle for either summary here so both read as ordinary controls, not expandable list rows. */
.jt-start-for-disclosure > summary {
    list-style: none;
}

/* Groups the Start/Backdate/"Start for…" controls inside a row's Actions cell (_WorkRowActions)
   without adding a box of its own -- `display: contents` keeps each child a direct flex item of
   `.jt-toolbar` exactly as if the wrapper were not there. It exists purely as a hook the phone-width
   rule below can hide as a unit; see the "Tree table reflow" section for why. */
.jt-row-action-extra {
    display: contents;
}

/* Start is the action the row exists for, so it carries the accent at rest rather than only on
   hover — the muted default belongs to the corrections sitting beside it. */
.jt-icon-button--primary {
    color: var(--jt-orange-600);
    border-color: var(--jt-orange-600);
}

/* An icon action whose consequence is destructive and not undoable in the UI (revoking a token).
   The glyph itself is the shared `jt-icon-remove` cross: the difference from an ordinary remove is
   consequence, not kind, so it is carried by colour rather than by a second glyph. */
.jt-icon-button--danger {
    color: var(--jt-red-700);
}

.jt-icon-button--danger:hover {
    color: var(--jt-red-700);
    border-color: var(--jt-red-700);
}

.jt-icon-summary-glyph {
    width: 1rem;
    height: 1rem;
}

/* The same glyph inside a labelled button, so a toolbar's "Start session" and a row's icon-only start
   are recognisably one action rather than two unrelated controls. It takes the button's own colour
   by inheritance and is purely redundant with the adjacent label, hence aria-hidden at every use. */
.jt-btn-glyph {
    width: 1rem;
    height: 1rem;
    margin-right: var(--jt-space-1);
    vertical-align: -0.15em;
    fill: currentcolor;
}

/* ---- Backdate/Start-for popovers -------------------------------------------------------------- */

/* The expansion a _BackdateTrigger or Start-for's <summary> reveals: a shared surface treatment for
   every popover, wherever it floats from (toolbar, table row, session row). A tinted surface and
   shadow mark it as a transient correction pane sitting over the page, not ordinary content. */
.jt-backdate-panel {
    background: var(--jt-orange-50);
    border: 1px solid var(--jt-line);
    border-radius: 0.625rem;
    padding: var(--jt-space-3) var(--jt-space-4);
    box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 0.15);
    min-width: 16rem;
    max-width: min(24rem, calc(100vw - 2rem));
}

/* _StartForTrigger's panel: positioned against its own <details> (below, via .jt-start-for-disclosure's
   own `position: relative`) rather than moved to <body> -- see that partial's own comment for why. */
.jt-backdate-panel--anchored {
    position: absolute;
    top: calc(100% + var(--jt-space-2));
    left: 0;
    z-index: var(--jt-z-popover);
}

/* The icon-only per-row Start-for trigger sits in a table's rightmost column, so its panel grows
   leftward from that edge instead of risking an overflow past the viewport. */
.jt-backdate-panel--anchor-end {
    left: auto;
    right: 0;
}

/* site.js's JavaScript upgrade of an --anchored panel: same panel, same place in the DOM, but pinned
   to the viewport at coordinates computed from its own trigger (`top`/`left` arrive inline), so it
   can flip above the trigger when there is no room below. The absolute anchoring above cannot do
   that, and worse, an absolutely positioned box inside a table does not extend the document's
   scrollable area -- a panel opened from a table's last row hangs below the end of the page where
   nothing can scroll to it. `right: auto` undoes --anchor-end: with `left` set inline as well, the
   two together would stretch the panel between both edges. */
.jt-backdate-panel--pinned {
    position: fixed;
    right: auto;
}

/* Every _BackdatePanel: site.js moves it to the end of <body> and sets its own top/left inline the
   first time its trigger opens it (see site.js's disclosure-toggle handler), so it floats over the
   page from wherever its trigger actually is, immune to any ancestor's overflow clipping. No default
   position here beyond `fixed` itself -- the inline top/left always arrive before it is unhidden. */
.jt-backdate-panel--floating {
    position: fixed;
    z-index: var(--jt-z-popover);
}

/* The positioned ancestor _StartForTrigger's --anchored panel measures itself against. */
.jt-start-for-disclosure {
    position: relative;
}

.jt-backdate-form {
    display: flex;
    flex-wrap: wrap;
    align-items: end;
    gap: var(--jt-space-2) var(--jt-space-3);
    margin: 0;
}

/* Scaled to ~70% of the ordinary form controls — this is a transient correction pane, not a page's
   primary form, so it shouldn't compete for visual weight. Scoped to tablet-and-up: below 768px
   .form-control is pinned to 16px (see the iOS auto-zoom comment above) and must stay that way. */
@media (min-width: 768px) {
    .jt-backdate-form .form-group {
        margin-bottom: 0;
    }

    .jt-backdate-form label {
        font-size: 0.6rem;
        margin-bottom: 0.1rem;
    }

    .jt-backdate-form .form-control {
        padding: 0.35rem 0.5rem;
        font-size: var(--jt-font-size-xs);
    }

    .jt-backdate-form .btn {
        padding: 0.35rem 0.55rem;
        font-size: var(--jt-font-size-xs);
    }

    .jt-backdate-form .jt-btn-glyph {
        width: 0.7rem;
        height: 0.7rem;
    }
}

/* ---- Tree table reflow --------------------------------------------------------------------- */

/* Which columns survive which width is decided in the markup with Bootstrap display utilities, not
   here — see CLAUDE.md. This section only records the policy those utilities implement and carries
   the handful of *visual* adjustments that ride along with it.

   `d-none d-lg-table-cell` (`.jt-col-secondary` cells): below lg these tables drop the columns that
   are one tap away on the row's own page — owner, priority, kind/archived, span, deadline. The
   Browse subtree table cuts its priority column one tier earlier still (`d-none d-xxl-table-cell`),
   since below the widest viewports its twelfth reads better as description.

   `d-none d-md-table-cell` (`.jt-col-cost`, `.jt-col-active`, `.jt-session-started` cells): cost is
   the one secondary column worth keeping at tablet width — it is what most people scanning the
   table are actually looking for — as is the active-session column, since which jobs are being
   worked on right now is a live fact that changes under the reader rather than something they can
   be sent to another page for. Both go at phone width: six-plus columns there would squeeze the
   name to a couple of characters per line and push the page into a horizontal scroll, which WCAG
   1.4.10 Reflow forbids. `.jt-session-started` is the same cut on the leaf's own Sessions table
   (_LeafWorkSessions) — Worked by, Finished (which already carries the Active status pill), and one
   Pause button are what a phone reader needs at a glance.

   `display: none` throughout is deliberate over a visually-hidden treatment: this is
   redundant-at-this-size content, not content withheld from assistive tech.

   `.jt-row-action-extra` is the one member of this policy that stays a hand-written breakpoint
   below, because Bootstrap ships no `d-*-contents` utility and `display: contents` is load-bearing
   for it. */

/* Visual companions to the lg column cut above — a token value and a min-width, neither of which is
   a Bootstrap utility. 991.98px is Bootstrap's own lg boundary; it is stated numerically because a
   media query cannot read a custom property. */
@media (max-width: 991.98px) {
    /* The name no longer competes with five other columns, so it can give the indent back. */
    .jt-tree-cell {
        min-width: 0;
    }

    :root {
        --jt-icon-size-kind: 1.5rem;
    }
}

/* Below md the row's own page is one tap away for Start/Backdate/"Start for…" too, and keeping them
   squeezed into a repeating cell is what forces the horizontal scroll the policy above exists to
   avoid. This cannot be `d-none d-md-block`: the wrapper is `display: contents` so its children are
   direct flex items of `.jt-toolbar`, and no Bootstrap utility restores that. 767.98px is
   Bootstrap's own md boundary. */
@media (max-width: 767.98px) {
    .jt-row-action-extra {
        display: none;
    }
}

/* A blocked row's stop palm takes the same red as the readiness pill it echoes, so the two never
   read as different facts (ADR 0043). It overrides `.jt-tree-flag`'s muted default. Sized to match
   the kind glyph it sits beside rather than the smaller default flag size — the stop palm's blocky
   geometry needs the extra room to read clearly next to a leaf/branch/root icon. */
.jt-tree-blocked {
    width: var(--jt-icon-size-kind);
    height: var(--jt-icon-size-kind);
    fill: var(--jt-red-700);
    color: var(--jt-red-700);
}

/* Leading position: a peer of the kind icon in `.jt-tree-label`'s own flex row, not a flag trailing
   the name text. `.jt-tree-label`'s flex `gap` already spaces it from the kind icon that follows, so
   `.jt-tree-flag`'s own left margin (sized for trailing use, e.g. the archived flag) would double up;
   same top-align treatment as `.jt-tree-icon` so it doesn't drop to the text baseline. */
.jt-tree-flag--leading {
    margin-left: 0;
    align-self: flex-start;
    margin-top: -0.15rem;
}

/* Marks a container holding more than this fetch expanded — the row's own name links into it. */
.jt-tree-more {
    margin-left: var(--jt-space-1);
    color: var(--jt-faint);
}

/* The span bar's column. `min-width` on the track alone could not hold this down: the table is
   auto-layout, so every column with no stated width gets a share of whatever is left over, and a
   block-level track with no intrinsic width takes that share happily. Stating a width here is what
   caps it -- the bar is a rough position-in-tree cue, and three characters' worth of it says as
   much as a wide one does. */
.jt-col-span {
    width: 3rem;
}

/* Nested-set span bar (ADR 0039 decision 3): purely structural/decorative -- the row's own text
   (kind chip, depth label, ordering) already conveys the same containment fact accessibly, so this
   is `aria-hidden` and carries no contrast requirement of its own (plan §6). */
.jt-tree-span-track {
    position: relative;
    display: block;
    height: 0.375rem;
    /* A quarter of its original 4rem: the bar shows roughly where a node sits in the tree's ordering,
       which is a background fact, not something a reader acts on — it does not deserve the width it
       was taking from the columns that are. Narrowing this alone was not enough; the "Position"
       heading was the real floor on the column's width and is now visually hidden. */
    min-width: 1rem;
    background-color: var(--jt-slate-50);
    border-radius: 999px;
    overflow: hidden;
}

/* The bar is an <svg> whose viewBox is 100 units wide, so the row's percentages are written
   straight onto the rect as `x`/`width` attributes. Presentation attributes, not a `style`
   attribute: `style-src 'self'` drops inline styles, which is what silently collapsed the
   previous version of this bar to zero width. The track keeps the pill shape and clips the rect,
   so the rect itself needs no radius (which non-uniform scaling would distort anyway). */
.jt-tree-span {
    display: block;
    width: 100%;
    height: 100%;
}

.jt-tree-span-fill {
    fill: var(--jt-slate-300);
}

/* ---- Breadcrumb (Jobs/Browse lineage trail) ----------------------------------------------- */

/* Carries the lineage trail alone — "Filter & search" sits beside the node record further down
   the page (Browse's Bootstrap row), not up here. */
.jt-browse-toolbar {
    margin-bottom: var(--jt-space-3);
}

.jt-browse-toolbar nav[aria-label="breadcrumb"] {
    min-width: 0;
}

nav[aria-label="breadcrumb"] ol {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    list-style: none;
    padding: var(--jt-space-2) var(--jt-space-3);
    margin: 0;
    font-size: var(--jt-font-size-sm);
    background: var(--jt-surface);
    border: 1px solid var(--jt-line);
    border-radius: 2rem;
    box-shadow: var(--jt-shadow-sm);
    color: var(--jt-muted);
    max-width: 100%;
}

nav[aria-label="breadcrumb"] li {
    white-space: nowrap;
}

nav[aria-label="breadcrumb"] li:last-child {
    color: var(--jt-ink);
    font-weight: 600;
}

nav[aria-label="breadcrumb"] li + li::before {
    content: "\203a";
    margin: 0 var(--jt-space-2);
    color: var(--jt-faint);
}

/* ---- Tables (ledger) ---------------------------------------------------------------------- */

/* A table's block wrapper: spacing and a width bound, deliberately *not* a scrolling region. A page
   scrolls as one unit (CLAUDE.md), so a table that will not fit reflows -- columns drop out at their
   breakpoints via Bootstrap display utilities and names wrap -- rather than hiding its right-hand
   columns behind a scrollbar a touch reader never sees, clipping any popover opened in a row, and
   giving `position: sticky` an inner box to stick to instead of the viewport. */
.jt-table-block {
    max-width: 100%;
}

/* Identifiers and money render in monospace for scan-ability. Ledger styling — a firm header
   rule, hairline rows, a warm orange hover wash. */
.table {
    width: 100%;
    margin-bottom: 0;
    border-collapse: separate;
    border-spacing: 0;
    background: var(--jt-surface);
    border: 1px solid var(--jt-line);
    border-radius: var(--jt-radius-lg);
    box-shadow: var(--jt-shadow-sm);
    color: var(--jt-ink);
}

/* Not sticky: with the page scrolling as one unit the only thing a sticky heading could stick to is
   the viewport, where `.app-header` (sticky, opaque, z-index 1030) already sits -- the headings would
   slide underneath it and be replaced by nothing. A heading row that scrolls away with its table is
   also the one that can never paint over, or under, the rows it labels. Headings wrap by default,
   too: with no horizontal scroller to absorb them, a `white-space: nowrap` heading ("Amount per
   hour") is a hard floor on its column's width and the usual reason a narrow viewport overflows.
   The nowrap carve-out below is for the columns that can never be that cause. */
.table thead th {
    background: var(--jt-surface-2);
    border-bottom: 1px solid var(--jt-line-strong);
    padding: 0.75rem 1rem;
    font-size: var(--jt-font-size-xxs);
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--jt-muted);
}

.table tbody td {
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--jt-line);
    vertical-align: middle;
}

/* A table cannot lay out narrower than the sum of its columns' min-content widths, so one long word --
   an employee's name, a job description, an identifier -- is a hard floor that pushes the whole table
   past a narrow viewport however many columns have already been dropped. `anywhere` is the only value
   that lowers min-content: `overflow-wrap: break-word` breaks lines but leaves the intrinsic minimum
   at the longest word, and `word-break: break-all` breaks even when there is room. So a cell only ever
   breaks mid-word as a last resort, and the reflow policy no longer depends on nobody having a long
   name. */
.table th,
.table td {
    overflow-wrap: anywhere;
}

/* Browse's child-node rows repeat often and carry no more than a name/icon/pill per cell, so a
   shorter row lets more of the tree show without scrolling — other tables keep the taller default.
   The row height itself is fixed at two lines of the default cell text (1.5rem/line at every
   breakpoint this file defines) plus this padding, so scanning a long list doesn't jump around;
   `overflow: hidden` clips anything that would otherwise push a row taller (a long name, several
   active-session badges) rather than letting it grow. */
.jt-browse-children-table tbody td {
    height: 3.375rem;
    padding-top: 0.1875rem;
    padding-bottom: 0.1875rem;
    overflow: hidden;
}

/* The Actions cell holds only fixed-size icon buttons, never content tall enough to need the
   row-height clamp above -- but that clamp's `overflow: hidden` also clips the Start-for
   popover, since its containing block (`.jt-start-for-disclosure`, position: relative) lives
   inside this cell. Excluding just this column lets the popover escape the row instead of being
   clipped to it (and painted over by the next row's background). */
.jt-browse-children-table tbody td:last-child {
    overflow: visible;
}

/* Flat search rows do not have `.jt-tree-text`, so their direct-child link owns the two-line clamp.
   Subtree rows clamp the complete title-and-markers run above instead. */
.jt-browse-children-table td > a.jt-preserve-whitespace {
    display: -webkit-inline-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Flat-row links remain atomic clamped boxes; bottom alignment keeps their trailing badges level
   with the final line. It is harmless for subtree markers, which now participate in that line. */
.jt-browse-children-table .jt-achievement-icon,
.jt-browse-children-table .jt-tree-more {
    vertical-align: bottom;
}

.jt-browse-children-table .jt-tree-text .jt-tree-flag {
    vertical-align: bottom;
}

.table tbody tr {
    transition: background-color var(--jt-transition);
}

.table tbody tr:last-child td {
    border-bottom: none;
}

.table tbody tr:hover {
    background-color: var(--jt-orange-50);
}

.table td.jt-id,
.table td.jt-amount,
.jt-amount {
    font-family: var(--bs-font-monospace);
    font-variant-numeric: tabular-nums;
}

/* Owner and Cost carry less reading weight than the row's own name/description, so they run a size
   smaller than the table body default — same treatment on the Browse tree/search tables and the
   awaiting-progress dashboard, which both reuse these column classes. */
.table td.jt-col-secondary,
.table td.jt-col-cost {
    font-size: var(--jt-font-size-sm);
}

/* These headings never wrap. A one-word label ("Deadline", "Archived") broken across two lines makes
   the header row taller than the data it labels and reads as two labels rather than one, and where
   the word is wider than its column the wrap buys nothing anyway — the column is no narrower for it.
   Safe here, and only here, precisely because every column in this set is already hidden below md or
   lg: it can never be the hard floor that pushes a narrow viewport into horizontal overflow, which
   is why the default above stays "headings wrap". A label still too wide for its share at the widths
   it does appear at is abbreviated in the markup instead — Browse's "Pri"/"Due", each carrying the
   full name for assistive tech — not left to wrap. */
.table th.jt-col-secondary,
.table th.jt-col-active,
.table th.jt-col-span {
    white-space: nowrap;
}

/* A cost reads "£1,234.56 / 12h 30m" — four break opportunities, in a column that competes for width
   with a description whose own text wraps freely. Under auto table layout the browser satisfies each
   column's min-content width first, so the most breakable column is the one it squeezes: left to
   wrap, cost collapses toward a character per line at mid widths while the description keeps its
   room. Holding the pair atomic makes the whole string cost's min-content width, so the column
   actually occupies the share its col-* allocation asks for.
   Qualified on .d-md-table-cell rather than applied to every .jt-col-cost: that is exactly the set of
   cost columns hidden below md, where there is room to spare. Prerequisites keeps its cost visible at
   phone width instead, and there an unbreakable string would be wider than the whole column and push
   the table into horizontal overflow — so that one is left free to wrap. */
.table th.jt-col-cost.d-md-table-cell,
.table td.jt-col-cost.d-md-table-cell {
    white-space: nowrap;
}

/* Owner carries even less reading weight than the other secondary columns above (Archived,
   Deadline), so it steps down one more tier and overrides the rule above. */
.table td.jt-col-owner {
    font-size: var(--jt-font-size-xs);
}

/* Utility: preserve line breaks/spacing in free-form text (titles, writeups, notes) that would
   otherwise be whitespace-collapsed by normal HTML flow. overflow-wrap keeps this safe inside
   narrow table columns, where an unbroken long word/line could otherwise force horizontal scroll. */
.jt-preserve-whitespace {
    white-space: pre-wrap;
    overflow-wrap: break-word;
}

/* Utility: a subtle inline mono chip for identifiers rendered outside tables. */
.jt-tag {
    display: inline-block;
    padding: 0.05rem 0.4rem;
    font-family: var(--bs-font-monospace);
    font-size: 0.85em;
    background: var(--jt-orange-50);
    border: 1px solid var(--jt-orange-100);
    border-radius: 0.375rem;
    color: var(--jt-orange-700);
}

.jt-muted {
    color: var(--jt-muted);
}

/* ---- Priority label ------------------------------------------------------------------------ */

/* _PriorityLabel's per-level modifier classes. High and Urgent carry a rule so far -- Low/Medium
   render as plain text pending a future pass giving each its own colour/weight (PriorityLabelModel).
   jt-red-600, not the darker/duller jt-red-700 used for validation/danger elsewhere: against this
   palette's own warm orange saturation, jt-red-700 reads as too close to brown/orange at a glance --
   this brighter, more saturated red stays unambiguous red beside it (still >4.5:1 on white, bold). */
.jt-priority--high,
.jt-priority--urgent {
    color: var(--jt-red-600);
    font-weight: 700;
}

/* ---- Overdue deadline ----------------------------------------------------------------------- */

/* A rendered "start by"/"finish by" deadline (InstantDisplay.IsPast) that has already passed --
   jt-red-600, the same brighter red .jt-priority--high/--urgent use and for the same reason: against
   this palette's warm orange saturation, the darker jt-red-700 reserved for validation/danger reads
   too close to brown/orange at a glance. Colour only, no weight -- unlike priority this sits inline in
   an ordinary table cell or sentence, not a label meant to be scanned for on its own. */
.jt-overdue {
    color: var(--jt-red-600);
}

/* ---- Page head + lede --------------------------------------------------------------------- */

.jt-page-head h1 {
    margin-bottom: var(--jt-space-2);
}

.jt-lede {
    font-size: 1.05rem;
    color: var(--jt-muted);
}

/* A quiet subtitle line naming the record/context a page is acting on. Wrapping/alignment/gap
   come from Bootstrap's d-flex flex-wrap align-items-baseline gap-2 on the markup. */
.jt-context {
    color: var(--jt-muted);
    font-size: 0.9375rem;
}

.jt-context strong {
    color: var(--jt-ink);
    font-weight: 650;
}

/* Bare <ul> lists used for prerequisites, siblings, blockers — render as a tidy stack of chips
   rather than default browser bullets. */
.jt-list {
    list-style: none;
    /* UA top margin only — the gap below is the block-component rule's. */
    margin-top: 0;
    padding: 0;
    max-width: var(--jt-measure-panel);
}

/* The inherited-blockers list isn't split into a two-column grid like Requires/Depends-on above
   it, so the narrow-content cap those share would leave it looking stranded on wide screens
   instead of filling the full-width card it sits in. */
.jt-list--wide {
    max-width: none;
}

/* Row layout, alignment, and gaps come from Bootstrap utilities in the markup. */
.jt-list > li {
    padding: 0.75rem 1rem;
    background: var(--jt-surface);
    border: 1px solid var(--jt-line);
    border-radius: var(--bs-border-radius-sm);
    box-shadow: var(--jt-shadow-sm);
    margin-bottom: var(--jt-space-2);
}

/* A single-purpose, one-action page (create/edit/move/decompose a node, change a password,
   assign a role…): centers the page head and its form/confirmation content as one narrow column
   instead of letting it hug the left edge of the wide .container on large screens. 44rem matches
   the narrow-content convention already used by dl/.jt-list/.jt-notice, so nothing inside needs
   its own width override. Not for dashboard-style pages that mix a form with a wide table or
   list (Browse, Work, AwaitingProgress, Prerequisites…) — those stay full width. */
.jt-page-narrow {
    max-width: var(--jt-measure-panel);
    margin-inline: auto;
}

/* ---- Auth screens ------------------------------------------------------------------------- */

.jt-auth {
    max-width: var(--jt-measure-auth);
    margin: var(--jt-space-5) auto;
}

.jt-auth .jt-form-card {
    box-shadow: var(--jt-shadow-lg);
}

.jt-auth-emblem {
    display: grid;
    place-items: center;
    width: 3.25rem;
    height: 3.25rem;
    margin-bottom: var(--jt-space-3);
    border-radius: 1rem;
    background: linear-gradient(140deg, var(--jt-orange-400), var(--jt-orange-600));
    box-shadow: var(--jt-glow);
    color: #fff;
    font-weight: 700;
}

/* A framed notice panel for empty/denied/informational states. */
.jt-notice {
    max-width: var(--jt-measure-field);
    padding: var(--jt-space-4);
    background: var(--jt-surface);
    border: 1px solid var(--jt-line);
    border-left: 3px solid var(--jt-orange-600);
    border-radius: var(--jt-radius-lg);
    box-shadow: var(--jt-shadow-sm);
}

/* Empty-state line used across list/table pages ("None.", "No jobs to show."). */
.jt-empty {
    padding: var(--jt-space-4);
    color: var(--jt-muted);
    background: var(--jt-surface-2);
    border: 1px dashed var(--jt-line-strong);
    border-radius: var(--bs-border-radius);
    max-width: var(--jt-measure-panel);
}

/* Caps a jt-card's width for content that reads badly stretched full-width, e.g. the Search form. */
.jt-card--narrow {
    max-width: var(--jt-measure-panel);
}

/* Compact client-side browse history — links only, sits below the children table. */
.jt-history {
    margin-top: var(--jt-space-4);
    max-width: var(--jt-measure-panel);
}

.jt-history-label {
    font-family: var(--bs-font-monospace);
    font-size: var(--jt-font-size-xxs);
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--jt-faint);
    margin: 0;
}

/* Sits beside the label at the same quiet weight — an occasional housekeeping action, not a
   destination competing with the breadcrumbs it clears. */
.jt-history-clear {
    padding: 0;
    border: 0;
    font-size: var(--jt-font-size-sm);
    line-height: 1.4;
    color: var(--jt-faint);
    text-decoration: underline;
}

.jt-history-clear:hover,
.jt-history-clear:focus-visible {
    color: var(--jt-ink);
}

.jt-history-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.125rem var(--jt-space-3);
    font-size: var(--jt-font-size-sm);
    line-height: 1.4;
}

.jt-history-list > li {
    margin: 0;
}

.jt-history-empty {
    color: var(--jt-faint);
    font-size: var(--jt-font-size-sm);
}

.jt-disclosure-summary {
    cursor: pointer;
    font-weight: 600;
}

/* A non-clickable list heading with no pointer affordance — labels the two always-shown lists
   (Requires / Depends-on) that share one card on Browse. */
.jt-list-heading {
    margin: 0;
    font-weight: 700;
}

.jt-disclosure-body {
    margin-top: var(--jt-space-3);
}

.jt-notice--error {
    border-left-color: var(--jt-red-700);
}

.jt-tree-row[data-tree-depth="0"] {
    padding-left: 1rem;
}

.jt-tree-row[data-tree-depth="1"] {
    padding-left: 2.5rem;
}

.jt-tree-row[data-tree-depth="2"] {
    padding-left: 4rem;
}

.jt-tree-row[data-tree-depth="3"] {
    padding-left: 5.5rem;
}

.jt-tree-row[data-tree-depth="4"] {
    padding-left: 7rem;
}

.jt-tree-row[data-tree-depth="5"] {
    padding-left: 8.5rem;
}

.jt-tree-row[data-tree-depth="6"] {
    padding-left: 10rem;
}

.jt-tree-row[data-tree-depth="7"] {
    padding-left: 11.5rem;
}

.jt-tree-row[data-tree-depth="8"] {
    padding-left: 13rem;
}

.jt-tree-row[data-tree-depth="9"] {
    padding-left: 14.5rem;
}

.jt-tree-row[data-tree-depth="10"] {
    padding-left: 16rem;
}
