:root {
    --text-color: #000000;
    --main-color: #0091bc;
    --light-main-color: #cce9f2;
    --dark-main-color: #004366;
    --competitor-id-color: #FF4511;
    --competitor-remplan-color: #d33136;
    --dark-complementary-color: #BC2B00;
    --light-complementary-color: #FF9B7E;
    --dark-tertiary-color: #FA7098;
    --light-tertiary-color: #FDB8CC;
    --dark-second-tertiary-color: #faf070;
    --dark-grey-color: #999999;
    --light-grey-color: #E0E0E0;
    --diverging-neutral-color: #f4f4f4;
    --text-heading-color: #464646;
    /* Heading letter-spacing — now zero on all three lines. These used to carry a fractional
       compensation (title 1.7px, source 0.15px) so the DOM table heading would line up with the
       ECharts CANVAS chart heading beside it. That never held across browsers/displays: fractional
       letter-spacing renders differently per engine (sub-pixel letter-spacing is Blink-only; Safari
       rounds it differently) and per device pixel ratio, so a value tuned on one Retina Chrome drifted
       on Safari and on non-Retina displays. The real fix (Option 1, June 2026) renders chart headings
       as DOM too (installHtmlHeader), so heading and table now use the SAME natural tracking and match
       in every browser — no compensation needed. Kept as tokens (at 0) so the export path can find the
       lines. The html2canvas image export applies a tiny per-line nudge in onclone so word spaces do
       not collapse at exactly zero (see table_widget.html and compositeChartDownload). */
    --table-header-title-letter-spacing: 0;
    --table-header-subtitle-letter-spacing: 0;
    --table-header-source-letter-spacing: 0;
    /* Heading geometry shared by the ECharts canvas heading (createTitleConfig in charts_util.js
       reads these at load) and the HTML table heading (.table-header-* below). Single source of
       truth so the two surfaces can never drift apart — see style-guide §3.11. The gaps measure
       from the right edge of the accent bar to the start of each text line. */
    --heading-bar-width: 22px;
    --heading-title-gap: 8px;
    --heading-subtitle-gap: 9px;
    --heading-source-gap: 10px;
    --heading-title-font-size: 32px;
    --heading-title-line-height: 40px;
    --heading-subtitle-font-size: 16px;
    --heading-subtitle-line-height: 22px;
    --heading-source-font-size: 8px;
    --heading-source-line-height: 16px;
    /* Distance from a section bar to the content below it — for chart and table sections that is
       the accent-bar heading, which sits flush at the top of the content (chart titles render at
       top: 0; the table heading is the first in-flow element). Applied via .section-content-gap on
       every section collapse so the gap is identical across charts, tables, maps, and lists. */
    --section-heading-gap: 24px;
    --lga-color: #4C86F7;
    --sa1-color: #F74C86;
    --muted: #BBBBBB;
    --table-hover-grey: #e8e8e8;
    --tooltip-highlight-color: rgba(0, 145, 188, 0.15);
    /* The Measure popover width, defined ONCE here and referenced by both the popover menu
       (.measure-control-menu) and the trigger that expands to match it on open
       (.measure-control-toggle.show), so the two can never drift apart (§3.11). */
    --measure-popover-width: 320px;
    /* The Focus popover's width, read by both the menu (.view-focus-switcher .dropdown-menu) and
       the trigger that expands to match it on open (> .dropdown-toggle.show), exactly as the
       Measure token above does. Wide enough for its longest row, the "Keep focus across pages"
       checkbox, which must not wrap. */
    --focus-popover-width: 17rem;

    --text-primary: #000000;
    --text-muted: #6c757d;
    --text-on-dark: #ffffff;
    --areas-highlight-gold: #ffd700;

    --success: #198754;
    --success-bg: #d1e7dd;
    --success-text: #0f5132;
    --warning: #ffc107;
    --warning-bg: #fff3cd;
    --warning-text: #664d03;
    --danger: #dc3545;
    --danger-bg: #f8d7da;
    --danger-text: #842029;
    --info: #0dcaf0;
    --info-bg: #cff4fc;
    --info-text: #055160;
    /* Mid-lightness teal for the funnel Upcoming-demo pill: a genuine lightness step
       between the light-cyan Aware and the dark-navy Demo, so the progression is
       monotonic and separable for red-green colour-blind viewers (white text ~5:1). */
    --denden-upcoming-demo-bg: #1a7a94;
    /* The Scheduling demo pill. Deliberately OFF the blue ramp: the three stages either
       side of it (Demo navy, Upcoming demo teal, Aware light cyan) already use the room
       that ramp has, and squeezing a fourth blue between the teal and the cyan would land
       within 1.2:1 of both. Lavender keeps the ramp's lightness order (0.05, 0.16, 0.54,
       0.85 relative luminance, so it still reads warmer-is-darker) while adding hue as a
       second channel, which is what actually separates it from its neighbours: 2.77:1
       against the Upcoming demo teal and 1.53:1 against the Aware cyan. Its text is
       --dark-main-color at 5.91:1, clearing WCAG 1.4.3's 4.5:1 for ordinary text. */
    --denden-scheduling-demo-bg: #cabaef;
    /* Funnel-metrics chart band colours: darker versions of the funnel pill hues. The
       pills are deliberately light (for dark text on the pill); a stacked chart needs the
       opposite, strong bands that hold their colour under the hover highlight. Same hue
       family per stage, just darker. */
    --denden-funnel-band-won: #198754;
    --denden-funnel-band-demo: #004366;
    --denden-funnel-band-upcoming-demo: #1a7a94;
    /* The deep-violet sibling of the lavender pill above, and the same reasoning: on the
       stacked chart it sits between the Upcoming demo teal and the Aware blue, whose own
       adjacent pair is only 1.45:1, and this clears both by more (1.62:1 and 2.36:1) while
       being the one band on the chart that is not a blue, a gold or a grey. White on it
       is 8.02:1, so the stage-diagram node can carry a light label. */
    --denden-funnel-band-scheduling-demo: #5e35b1;
    --denden-funnel-band-aware: #2596be;
    --denden-funnel-band-disengaged: #d39e00;
    --denden-funnel-band-renewed-not-aware: #6c757d;
    --denden-funnel-band-not-aware: #adb5bd;
    /* Arrow colours for the funnel stage diagram, by what the move MEANS rather than by
       which stage it leaves. They are not the band colours: a band is a large filled area
       that only has to be told from its neighbour, while an arrow is a 2.5px line that has
       to clear WCAG 1.4.11's 3:1 against the white plate on its own. The Disengaged band
       (#d39e00) is 2.42:1 and fails that as a line, hence the darker decay gold. */
    --denden-funnel-edge-forward: #00546b;
    --denden-funnel-edge-decay: #8a6500;
    /* Deliberately NOT the Won green: the same hue for "a council wakes up" and for the Won
       node would put one colour on two meanings in a single drawing. */
    --denden-funnel-edge-return: #1f6f5c;
    /* The stage diagram is drawn against a fixed light plate in both themes, because its
       node fills are the funnel band colours and those are chosen for light. */
    --denden-stage-diagram-plate: #ffffff;
    /* GitHub-style activity contribution heatmap: empty day, then a light-to-dark green
       ramp (brighter = busier). */
    --activity-heat-empty: #ebedf0;
    --activity-heat-1: #9be9a8;
    --activity-heat-2: #40c463;
    --activity-heat-3: #30a14e;
    --activity-heat-4: #216e39;
    --muted-bg: #e9ecef;
    --muted-text: #495057;
    /* Bootstrap's btn-outline-secondary border, named so the area picker's chips can match the
       filter's small-area buttons exactly rather than approximating them. */
    --btn-outline-secondary-border: #6c757d;

    --shadow-focus: 0 0 0 0.25rem rgba(0, 145, 188, 0.25);
    --highlight-ring: var(--main-color);
    --shadow-highlight: 0 0 0 3px var(--highlight-ring);

    /* Light floating-surface tokens (style-guide §1). Named in the guide but not
       previously defined here; --border-subtle was already referenced (base.css
       ~l.384) without a definition. Values match what the file already uses. */
    --surface-card: #ffffff;
    --border-subtle: var(--light-grey-color);
    --radius-card: 8px;
    /* Named by the style guide (§1.4, §2.8) and referenced in the file, but never defined until now,
       so every author had to fall back to a raw value the guide then flagged. */
    --radius-compact: 0.25rem;
    --radius-pill: 999px;
    --shadow-popover: 0 2px 8px rgba(0, 0, 0, 0.08);

    /* Hierarchy vocabulary, shared by the forecast tree view and the area picker so the two places
       that draw a parent/child list indent and animate identically rather than by coincidence. */
    --forecast-tree-indent: 2.25rem;
    --forecast-tree-chevron-half: 0.925rem;
    --forecast-tree-transition: 0.15s ease;

    /* Spacing scale (§1.2). Same values the file already used as raw rem literals. */
    --space-3xs: 0.125rem;
    --space-2xs: 0.25rem;
    --space-xs: 0.5rem;
    --space-sm: 0.75rem;
    --space-md: 1rem;

    /* A greyed-out control still has to be readable, so this is a real text grey (7.6:1 on white),
       not the decorative --muted (#BBBBBB, 1.9:1) that was doing the job before. */
    --text-disabled: var(--muted-text);

    /* Tonal button — a quiet, brand-coloured solid action for workbench surfaces
       (style-guide §2.1). The middle rung between btn-primary (the one loud CTA)
       and btn-secondary (grey chrome): unmistakably a brand action, but it does
       not compete with the page's single loud primary. Dark-navy text on a
       light-cyan tint clears WCAG AA comfortably (~10:1). */
    --btn-tonal-bg: var(--light-main-color);
    --btn-tonal-text: var(--dark-main-color);
    --btn-tonal-hover-bg: #a8d9e8;
    --btn-tonal-active-bg: #8ccadf;

    --font-body: 1rem;
    --font-body-sm: 0.875rem;
    --font-body-xs: 0.75rem;
    /* The compact control buttons: the category pills above the area list and the branch-select
       buttons inside it. Was a raw 0.7rem on .control-btn alone, which is how the branch buttons came
       to sit a size larger than the row of pills they line up under. */
    --font-control-btn: 0.7rem;
    --font-badge: 0.6rem;
    --font-display: 2.5rem;
    --font-heading-xl: 2rem;
    --font-heading-lg: 1.5rem;
    --font-heading-md: 1.25rem;
    --font-heading-sm: 1.125rem;

    /* Responsive Breakpoints */
    --desktop-breakpoint: 992px;

    /* Badge Colors - see style guide §4.5 for the standing contrast exception */
    --badge-mark-main: var(--main-color);
    --badge-mark-complementary: var(--light-complementary-color);
    --badge-demo-color: var(--badge-mark-complementary);
    --badge-temporary-color: var(--badge-mark-complementary);
    --badge-development-color: var(--badge-mark-main);
    --badge-public-color: var(--badge-mark-main);
    --badge-internal-color: var(--badge-mark-main);
    --badge-active-color: var(--badge-mark-main);
    --badge-pending-color: var(--badge-mark-complementary);

    /* Badge Icons */
    --badge-demo-icon: "D";
    --badge-temporary-icon: "T";
    --badge-public-icon: "bi bi-globe";
    --badge-internal-icon: "bi bi-lock-fill";
    --badge-active-icon: "bi bi-patch-check-fill";
    --badge-pending-icon: "bi bi-patch-exclamation-fill";

    --badge-scenario-right-with-purpose: 200px;
    --badge-scenario-right-with-purpose-mobile: 155px;

    /* Responsive spacing */
    --block-spacing: 0.5rem;
}

html {
    font-size: 18px; /* looks better on small screens */
    color: var(--text-color);
}

a {
    color: var(--main-color);
}

a:hover {
    color: var(--dark-main-color);
}

/* START sticky footer desing */

html, body {
    height: 100%;
    margin: 0;
}

#page-container {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

#content-wrap {
    flex: 1;
}

/* END sticky footer desing */

/* Larger screens */
@media (min-width: 1024px) {
    html {
        font-size: 18px; /* 18px = Bootstrap5 base size */
    }
}

.container-narrow {
    max-width: 500px;
}

.container-dataset {
    max-width: 750px;
}

.dataset-list .container-dataset {
    max-width: 500px;
}

.container-medium {
    max-width: 768px;
}

.table-primary,
.table-primary > th,
.table-primary > td {
    background-color: var(--light-main-color) !important;
    white-space: nowrap;
    text-align: left;
}

.table {
    width: auto;
}

.data-table table tr:last-child {
    font-weight: bold;
}

.data-table.no-bold-last-row table tr:last-child {
    font-weight: normal;
}

.highlight-row {
    background-color: var(--light-complementary-color);
}

.map-container {
    position: relative;
    width: 100%;
    padding-bottom: 56%; /* 16:9 Aspect Ratio */
}

.leaflet-container { /* all maps */
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    height: 100%;
    width: 100%;
}

#map .leaflet-interactive:focus {
    outline: none; /* Remove the blue border */
}

body #map .leaflet-attribution-flag {
    display: none !important; /* hide the leaflet flag —no politcal messages on our platform */
}

.medium {
    font-size: 1rem;
}

.small {
    font-size: 0.75rem;
}

.extra-small {
    font-size: 0.5rem;
}

/* The pill that names what kind of thing something is — the area type beside a page heading, the
   same area type in the area picker, the explainer type. Declared once here; .title_tag adds only
   the offset a page heading needs, and the explainer-type rules below override just the colour. */
.type-tag {
    background-color: var(--main-color);
    font-size: 0.5rem;
}

.title_tag {
    position: relative;
    top: -1rem;
}

/* Keeps the forecast/place name and its type pill on the same line — the pill must never wrap
   away from the place it labels. The title text before it (e.g. "Population of") still wraps. */
.title-place-tag {
    white-space: nowrap;
}

/* The explainer type colours, wherever an explainer type pill appears (detail heading, search
   results). One declaration per type, on the design tokens rather than repeated raw hex. */
.title_tag.explainer-type-feature,
.smart-search-results .badge.explainer-type-feature {
    background-color: var(--main-color);
}

.title_tag.explainer-type-concept,
.smart-search-results .badge.explainer-type-concept {
    background-color: var(--dark-complementary-color);
}

.title_tag.explainer-type-glossary,
.smart-search-results .badge.explainer-type-glossary {
    background-color: var(--dark-tertiary-color);
}

.denden-role-tag { color: var(--text-on-dark); }
.denden-role-place_info { background-color: var(--main-color); }
.denden-role-council { background-color: var(--dark-main-color); }
.denden-role-external { background-color: var(--dark-tertiary-color); }
/* min-width floors a non-zero contribution at a visible mark. The scale spans every
   fold, so one high-volume address can push a real person's share below a tenth of a
   percent, and "no bar at all" already means "wrote nothing" — the two states must
   not collapse into each other. The printed word count stays exact. */
.denden-participants-bar { background-color: var(--main-color); min-width: 2px; }

/* Meetings, in the secondary brand tone. Two bars in one colour, stacked, read as one
   measure split across two lines rather than two measures of different things. */
.denden-participants-bar--meetings {
    background-color: var(--dark-main-color);
}
.denden-pipeline-tag { background-color: var(--dark-complementary-color); color: var(--text-on-dark); }

/* Home-page "unassigned councils" alert: a shared list of gaps with readable (not
   uppercase-tag) council chips. It shares .denden-standing-note for its accent, so this
   keeps its own spacing only. */
.denden-unassigned-councils { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.denden-unassigned-councils a {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    background-color: var(--surface-card);
    border: 1px solid var(--dark-tertiary-color);
    color: var(--text-color);
    text-decoration: none;
}
.denden-unassigned-councils a:hover { text-decoration: underline; }
.denden-unassigned-councils a:focus-visible { box-shadow: var(--shadow-focus); outline: none; }

.denden-competitor-product-tag {
    background-color: var(--dark-tertiary-color);
    color: var(--text-on-dark);
    margin-right: 0.25rem;
}
.denden-competitor-product-tag--id { background-color: var(--competitor-id-color); }
.denden-competitor-product-tag--remplan { background-color: var(--competitor-remplan-color); }
.denden-competitor-product-tag--past.denden-competitor-product-tag--id,
.denden-competitor-product-tag--past.denden-competitor-product-tag--remplan {
    background-color: transparent;
    color: var(--dark-main-color);
}
.denden-competitor-product-tag--past.denden-competitor-product-tag--id {
    border: 1px solid var(--competitor-id-color);
}
.denden-competitor-product-tag--past.denden-competitor-product-tag--remplan {
    border: 1px solid var(--competitor-remplan-color);
}

.denden-inline-edit-toggle {
    color: var(--dark-main-color);
    text-decoration: underline;
    margin-left: 0.25rem;
    vertical-align: baseline;
}
.denden-inline-edit-toggle:hover { text-decoration: none; }
/* A bare .btn has no variant, so Bootstrap's --bs-btn-focus-shadow-rgb is unset and
   its focus box-shadow resolves to nothing, while the same rule still applies
   outline: 0. Without this, these pencils have no visible focus at all. */
.denden-inline-edit-toggle:focus-visible {
    box-shadow: var(--shadow-focus);
}

/* A real <button> that reads as an in-content text link, using the brand link
   colour (Bootstrap's .btn-link would force its default blue). */
.denden-link-button {
    padding: 0;
    border: 0;
    background: none;
    color: var(--main-color);
    text-decoration: underline;
    vertical-align: baseline;
}
.denden-link-button:hover { color: var(--dark-main-color); }
.denden-link-button:focus-visible {
    outline: 2px solid var(--main-color);
    outline-offset: 2px;
}

.denden-urgent-list .denden-task-row {
    border-left: 3px solid var(--success);
    padding-left: 0.75rem;
}

.denden-pc-meta {
    margin: 0;
}
.denden-pc-meta-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.15rem 0;
}
.denden-pc-meta-row dt {
    font-weight: normal;
    margin: 0;
    flex: 0 0 auto;
}
.denden-pc-meta-row dd {
    margin: 0;
    flex: 1 1 auto;
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.denden-inline-input { width: auto; }
.denden-inline-input[multiple] { min-width: 18rem; }
.denden-inline-input[type="text"] { min-width: 22rem; }

/* ONE pill height in DenDen: 16px, every pill, pressable or not.

   It was SIX heights (18 Aug 2026, measured on one council page) because each pill kept
   whatever its own rule produced: role tag and Sponsor 14.8, status pills 15, mailbox
   16.8, pipeline tag 18.3, competitor product 20.2, and 24 on the three that shared the
   touch-target rule.

   **The 24px WCAG 2.5.8 (AA) target is met by an INVISIBLE box, never a taller pill**
   (see the hit-area rule below). The floor is about the area that accepts a pointer,
   not about how big the pill looks, and 24px pills make a row of small labels beside
   two large ones. Growing them was tried and it is what made the rows read as broken.

   Reached with `min-height` and never with padding: at `--font-badge` (0.6rem) the
   padding needed would land off the 4px spacing grid and reflow every row. `inline-flex`
   with centring makes the spare height fall evenly above and below the word.

   `line-height: 1.1`, and the number is load-bearing in both directions. The inherited
   1.5 makes a pill 20.2px on its own, so `min-height: 16px` never governs and the whole
   rule does nothing. A flat `1` makes the line box equal the em box, so a label long
   enough to wrap has its ascenders and descenders touching on consecutive lines. 1.1
   leaves 15.88px of content plus padding, just under the 16px min, so every single-line
   pill lands on exactly 16 while a wrapped one stays legible.

   A pill is a SINGLE-LINE component. If a label is long enough to wrap it has outgrown
   this and wants a different treatment, not a taller pill.

   NO `flex-wrap: wrap` here. A pill's text is one anonymous flex item, so it wraps
   because the box is width-constrained, never because of that declaration.

   `.denden-funnel-stage` is in here BY ITS ROW, not by its class, and that is
   deliberate. On a task row it stands beside "Current client" and has to match it. On
   the funnel board the same class is a card carrying a council's name, and on a
   council's own page it is a lone link in the meta list, where 16px uppercase would be
   wrong. One class, two jobs, so only the row's job joins this list.

   Deliberately NOT in here: `.denden-freshness-dot` (a dot, not a pill) and
   `.denden-demo-count` (a numeric counter, round on purpose). */
.denden-role-tag,
.denden-sponsor-pill,
.denden-departed-pill,
.denden-do-not-contact-pill,
.denden-contact-status-pill,
.denden-mailing-pill,
.denden-linkedin-pill,
.denden-mailbox-pill,
.denden-competitor-product-tag,
.denden-pc-status,
.denden-pipeline-tag,
.denden-task-open-badge,
.denden-task-type-badge,
.denden-task-row .denden-funnel-stage {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 16px;
    line-height: 1.1;
    font-size: var(--font-badge);
    text-transform: uppercase;
    font-weight: 600;
    vertical-align: middle;
}

/* Horizontal padding only, because the shared `min-height` owns the vertical. Two
   values, and the difference is the shape of the pill rather than anything it means:
   8px on the fully-rounded (`border-radius: 999px`) ones, so the text clears the curve.
   `.denden-pipeline-tag` is in here because it had NO padding of its own and was falling
   through to Bootstrap's `.badge` (0.35em 0.65em), which put it at 18.3px, off the grid
   and off this list's one height. */
.denden-role-tag,
.denden-sponsor-pill,
.denden-departed-pill,
.denden-do-not-contact-pill,
.denden-contact-status-pill,
.denden-mailing-pill,
.denden-linkedin-pill,
.denden-competitor-product-tag { padding: 2px 6px; }
.denden-pc-status,
.denden-pipeline-tag,
.denden-task-open-badge,
.denden-task-type-badge { padding: 2px 8px; }
/* `.denden-funnel-stage` is deliberately absent from both padding groups: its own rule
   already sets 2px 8px, so listing it here would change nothing and read as though it
   did. */
/* Fully rounded on a task row, because it sits immediately beside "Current client"
   (`.denden-pc-status`, 999px) and a 10px corner next to a 999px one reads as two
   different kinds of thing. Its own rule keeps 10px, which is right on the funnel
   board where the same class draws a card. */
.denden-task-row .denden-funnel-stage {
    border-radius: 999px;
    letter-spacing: 0.02em;
}
/* A pill with a BORDER takes 1px vertical, because `box-sizing: border-box` counts the
   border: at 2px its natural height clears the shared 16px min and the min stops
   governing, leaving it a pixel or two taller than everything beside it. Two pills have
   one: the mailbox pill always, and a competitor product in its "past" (outline)
   variant. */
.denden-mailbox-pill { padding: 1px 6px; }
.denden-competitor-product-tag--past { padding-top: 1px; padding-bottom: 1px; }

/* The two pills that print somebody ELSE'S name and must not be recased: a competitor
   product (".id Community Profile (profile.id)") and a council ("also: Moorabool").
   Neither was uppercase before joining the shared rule, and a brand or a place name is
   not ours to shout. */
.denden-competitor-product-tag,
.denden-pipeline-tag { text-transform: none; }

/* THE 24px TOUCH TARGET, as an invisible box on the pills you can actually press.
   `inset: -4px 0` on a positioned pseudo-element lifts a 16px pill's target to 24px
   while the pill still reads the same size as the labels beside it.

   **QUALIFIED BY ELEMENT, and that is not tidiness.** Three of these classes are also
   rendered on inert `<span>`s (`_updates_pill.html` and `_linkedin_pill.html` each have
   one). A class-only selector gave those labels a positioned box proud of their own
   edges, which paints above neighbouring inline content and can swallow a real link's
   clicks for no benefit at all. Same qualification the focus rule below already uses.

   Horizontally it is exactly the pill's own width, so a neighbour 4px away keeps its
   clicks. VERTICALLY the band needs 8px between wrapped rows or two bands overlap over
   visually empty space, which is why `.denden-pill-row` exists and why any container
   where a pressable pill can wrap must use it. One destination is a no-confirmation
   submit that records an outreach action, so a stray click there is not free.

   Verify with `document.elementFromPoint` 3px above and below the visible edge, on a
   WRAPPED row rather than a single-line one. */
a.denden-mailing-pill,
button.denden-mailing-pill,
button.denden-linkedin-pill,
a.denden-pipeline-tag { position: relative; }
a.denden-mailing-pill::after,
button.denden-mailing-pill::after,
button.denden-linkedin-pill::after,
a.denden-pipeline-tag::after {
    content: '';
    position: absolute;
    inset: -4px 0;
}

/* Any row where a pressable pill can wrap onto a second line. `row-gap` clears the two
   4px hit-area bands; the column gap stays each caller's business, so it is set here
   per row rather than left to a Bootstrap `gap-*` utility (whose `!important` would
   win over this anyway). */
.denden-pill-row { display: flex; flex-wrap: wrap; row-gap: 8px; }
.denden-pill-row--tight { column-gap: 0; }
.denden-pill-row--spaced { column-gap: 4px; }

.denden-contact-status-pill { color: var(--text-on-dark); }
.denden-contact-status--established { background-color: var(--success); }
.denden-contact-status--undeliverable { background-color: var(--danger); }
.denden-contact-status--reached-out { background-color: var(--warning); color: var(--warning-text); }
.denden-contact-status--none { background-color: var(--dark-tertiary-color); }

/* Product-update mailing-list status pill. Each state uses a light-bg / dark-text
   token pair (contrast-safe) so the status word is legible; on_list is the strong
   solid-green "win". Muted grey for the dead-end states. */
.denden-mailing--invitable { background-color: var(--info-bg); color: var(--info-text); }
.denden-mailing--invite_tasked { background-color: var(--warning-bg); color: var(--warning-text); }
.denden-mailing--invited { background-color: var(--success-bg); color: var(--success-text); }
.denden-mailing--on_list { background-color: var(--success); color: var(--text-on-dark); }
.denden-mailing--pending { background-color: var(--info-bg); color: var(--info-text); }
.denden-mailing--opted_out { background-color: var(--muted-bg); color: var(--muted-text); }
.denden-mailing--declined { background-color: var(--muted-bg); color: var(--muted-text); }
.denden-mailing--not_asked { background-color: var(--muted-bg); color: var(--muted-text); }

/* LinkedIn connection pill, per operator. Shape shared with the two pills above; both
   answer "where are we with this person on a channel we do not own", so they read as
   siblings. Colours follow the mailing pill's meanings, with one deliberate exception:
   "not checked" is info blue where the mailing pill's never-asked state is muted grey,
   because this one IS the invitation to act and a fresh council should show it. */
.denden-linkedin--unknown { background-color: var(--info-bg); color: var(--info-text); }
.denden-linkedin--invited { background-color: var(--warning-bg); color: var(--warning-text); }
.denden-linkedin--connected { background-color: var(--success); color: var(--text-on-dark); }
.denden-linkedin--not_found { background-color: var(--muted-bg); color: var(--muted-text); }
.denden-funnel-stage {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 10px;
    font-size: var(--font-body-xs);
    font-weight: 600;
    white-space: nowrap;
}
.denden-funnel-stage--won { background-color: var(--success-bg); color: var(--success-text); }
.denden-funnel-stage--demo { background-color: var(--dark-main-color); color: var(--text-on-dark); }
.denden-funnel-stage--upcoming_demo { background-color: var(--denden-upcoming-demo-bg); color: var(--text-on-dark); }
.denden-funnel-stage--scheduling_demo { background-color: var(--denden-scheduling-demo-bg); color: var(--dark-main-color); }
.denden-funnel-stage--aware { background-color: var(--info-bg); color: var(--info-text); }
.denden-funnel-stage--disengaged { background-color: var(--warning-bg); color: var(--warning-text); }
.denden-funnel-stage--renewed_not_aware { background-color: var(--muted-bg); color: var(--muted-text); }
.denden-funnel-stage--not_aware { background-color: var(--light-grey-color); color: var(--muted-text); }
a.denden-funnel-stage { text-decoration: none; }
a.denden-funnel-stage:hover { text-decoration: underline; }
a.denden-funnel-stage:focus-visible { box-shadow: var(--shadow-focus); outline: none; }
/* The global a:hover colour outranks the stage classes and turned pill text
   dark; on the dark demo pill it vanished entirely. Each stage keeps its own
   text colour on hover. */
a.denden-funnel-stage--won:hover { color: var(--success-text); }
a.denden-funnel-stage--demo:hover { color: var(--text-on-dark); }
a.denden-funnel-stage--upcoming_demo:hover { color: var(--text-on-dark); }
a.denden-funnel-stage--scheduling_demo:hover { color: var(--dark-main-color); }
a.denden-funnel-stage--aware:hover { color: var(--info-text); }
a.denden-funnel-stage--disengaged:hover { color: var(--warning-text); }
a.denden-funnel-stage--renewed_not_aware:hover { color: var(--muted-text); }
a.denden-funnel-stage--not_aware:hover { color: var(--muted-text); }

/* Funnel stage diagram (denden/funnel_stages.html). Font sizes here are SVG user units, not
   CSS pixels: see style guide §1.6. */
/* A visible edge, because the drawing is wider than the card on every window. Won sits
   at the far right and `overflow-x: auto` alone shows nothing to scroll towards on a
   machine that hides scrollbars until they move, so the last stage reads as absent
   rather than as off-screen.

   An OVERLAY, not a background. The first attempt painted the fade as a background-image
   on this container, which never appeared: the SVG inside is an opaque child covering the
   whole painting area, the gradient ran white-into-white against the plate, and
   `background-attachment: local` pinned it to the far end of the scrollable area rather
   than to the visible edge. A sticky pseudo-element sits above the plate and stays where
   the eye is. */
/* Client scheduling principles. Each rule leads with a bolded sentence that carries the
   rule itself, so the list can be skimmed on the bolded lines alone and read in full only
   where it matters. The lead needs its own line for that to work. */
.denden-principles-list > li {
    margin-bottom: var(--space-md);
}

.denden-principles-list > li:last-child {
    margin-bottom: 0;
}

.denden-principles-list > li > strong {
    display: block;
}

.denden-principles-settings td:last-child,
.denden-principles-settings th:last-child {
    white-space: nowrap;
}

.denden-stage-diagram-scroll {
    overflow-x: auto;
    position: relative;
    display: flex;
}
.denden-stage-diagram-scroll::after {
    content: "";
    position: sticky;
    right: 0;
    flex: 0 0 2.5rem;
    margin-left: -2.5rem;
    align-self: stretch;
    pointer-events: none;
    background: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.16));
}
.denden-stage-diagram-scroll:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.denden-stage-diagram {
    display: block;
    width: 100%;
    height: auto;
    /* Matches the viewBox width, so user units render 1:1 as CSS pixels and the authored
       font sizes below ARE the rendered sizes (style guide §1.6). The container scrolls
       rather than scaling the type down into illegibility. */
    min-width: 1660px;
    background: var(--denden-stage-diagram-plate);
    border-radius: var(--radius-card);
}
.denden-stage-pool {
    fill: var(--light-grey-color);
    stroke: var(--denden-funnel-band-renewed-not-aware);
    stroke-width: 2;
    stroke-dasharray: 7 5;
}
.denden-stage-pool-label { font-size: 15px; font-weight: 700; fill: var(--text-heading-color); }
.denden-stage-name { font-size: 17px; font-weight: 600; }
.denden-stage-name--dark { fill: var(--text-primary); }
.denden-stage-name--light { fill: var(--text-on-dark); }
.denden-stage-node--not-aware { fill: var(--denden-funnel-band-not-aware); }
.denden-stage-node--disengaged { fill: var(--denden-funnel-band-disengaged); }
.denden-stage-node--renewed-not-aware { fill: var(--denden-funnel-band-renewed-not-aware); }
.denden-stage-node--aware { fill: var(--denden-funnel-band-aware); }
.denden-stage-node--scheduling-demo { fill: var(--denden-funnel-band-scheduling-demo); }
.denden-stage-node--upcoming-demo { fill: var(--denden-funnel-band-upcoming-demo); }
.denden-stage-node--demo { fill: var(--denden-funnel-band-demo); }
.denden-stage-node--won { fill: var(--denden-funnel-band-won); }
.denden-stage-marker--forward { fill: var(--denden-funnel-edge-forward); }
.denden-stage-marker--decay { fill: var(--denden-funnel-edge-decay); }
.denden-stage-marker--return { fill: var(--denden-funnel-edge-return); }
.denden-stage-edge { fill: none; stroke-width: 2.5; }
.denden-stage-edge--forward { stroke: var(--denden-funnel-edge-forward); }
.denden-stage-edge--decay { stroke: var(--denden-funnel-edge-decay); }
/* Hand-set, not derived. Every other edge on this diagram is something the data
   said; these two are something an operator said, and the prose asserts that twice
   while the picture drew them identically. A dashed stroke is the fourth cue, so
   the eye can pick out "we say this" without reading the rules list. The dash
   pattern is in user units, matching the viewBox, so it scales with the drawing. */
.denden-stage-edge--hand-set { stroke-dasharray: 8 6; }
.denden-stage-edge--return { stroke: var(--denden-funnel-edge-return); stroke-width: 3; }
.denden-stage-edge-label { font-size: 14px; fill: var(--denden-funnel-edge-forward); }
.denden-stage-edge-label--decay { fill: var(--denden-funnel-edge-decay); }
.denden-stage-edge-label--return { fill: var(--denden-funnel-edge-return); font-weight: 600; }
.page-content {
    padding-bottom: 32px;
}
.denden-funnel-board {
    display: flex;
    align-items: stretch;
    gap: 12px;
    overflow-x: auto;
}
.denden-funnel-col {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
}
.denden-funnel-substacks {
    flex: 1;
    display: flex;
    align-items: stretch;
    gap: 4px;
}
.denden-funnel-col-heading {
    font-size: var(--font-body-sm);
    font-weight: 600;
    color: var(--muted-text);
    text-align: center;
    margin-bottom: 8px;
}
.denden-funnel-col-stack {
    flex: 1;
    min-width: 140px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
}
/* Colour key in the Not engaged popover: one swatch per stage, filled with that stage's
   own card colour so the popover reads as a legend for the column beneath it. The border is
   load-bearing, not decoration: the three fills sit within about 1.1:1 of each other and of
   the white popover, so an unbordered swatch would be invisible. The border gives each chip
   an edge, and the label beside it carries the meaning regardless (WCAG 1.4.1), which is why
   the swatch itself is aria-hidden. */
.denden-funnel-key {
    display: inline-block;
    width: 14px;
    height: 14px;
    border-radius: var(--radius-compact);
    border: 1px solid var(--dark-grey-color);
    vertical-align: -2px;
    margin-right: 6px;
}
.denden-funnel-card {
    position: relative;
    display: block;
    text-align: center;
    overflow: hidden;
}
.denden-funnel-card-name {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Badges pin to the pill's right edge; the symmetric padding (only when badges
   exist) keeps the name centred on the pill as a whole, not centred-then-shoved. */
.denden-funnel-card .denden-demo-counts {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
}
.denden-funnel-card:has(.denden-demo-counts) .denden-funnel-card-name {
    padding: 0 44px 0 8px;
}
.denden-demo-counts {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    vertical-align: middle;
}
.denden-demo-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 8px;
    font-size: var(--font-badge);
    font-weight: 700;
    line-height: 1;
    /* The white halo keeps the badge's fill-vs-outline distinction legible on
       the dark demo and won pills, mirroring the timeline squares' keyline. */
    box-shadow: 0 0 0 1.5px var(--text-on-dark);
}
.denden-demo-count--held {
    background: var(--dark-complementary-color);
    color: var(--text-on-dark);
}
/* Hollow = upcoming, matching the timeline's demo squares; the number inherits
   the surrounding text colour so it stays readable on any pill background. */
.denden-demo-count--upcoming {
    border: 2px solid var(--dark-complementary-color);
    color: inherit;
}
.denden-funnel-won-lane {
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid var(--light-grey-color);
}
.denden-funnel-won-heading {
    text-align: left;
}
.denden-funnel-won-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
/* The actionable pills (invite/reopen buttons and the subscribed link) read as
   clickable and carry the mandated focus ring; inert pills are plain. */
button.denden-mailing-pill,
a.denden-mailing-pill,
button.denden-linkedin-pill { cursor: pointer; text-decoration: none; }
button.denden-mailing-pill:hover,
a.denden-mailing-pill:hover,
button.denden-linkedin-pill:hover { filter: brightness(0.95); }
button.denden-mailing-pill:focus-visible,
a.denden-mailing-pill:focus-visible,
button.denden-linkedin-pill:focus-visible { box-shadow: var(--shadow-focus); outline: none; }

/* Sponsorship is what makes a council a client, so its pill sits beside the role tag
   and matches its geometry exactly (padding, weight, vertical-align): the two are
   adjacent on one line, and any divergence lands them on different baselines.

   Green, and deliberately not the do-not-contact red or the departed navy. Those two
   record a decision to stop writing to somebody; this one records that they are the
   reason we have a client at all. */
.denden-sponsor-pill {
    color: var(--text-on-dark);
    background-color: var(--success);
}

.denden-departed-pill {
    color: var(--text-on-dark);
    background-color: var(--dark-main-color);
}

.denden-do-not-contact-pill {
    color: var(--text-on-dark);
    background-color: var(--danger);
}

.denden-freshness-dot {
    display: inline-block;
    margin-left: 0.3em;
    font-size: 0.8em;
    line-height: 1;
    cursor: default;
    border-radius: 50%;
    transition: box-shadow 120ms ease-in-out;
}
.denden-freshness-dot:hover {
    box-shadow: 0 0 0 0.15em var(--border-subtle);
}
.denden-freshness-dot:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}
.denden-freshness-dot--fresh { color: var(--success); }
.denden-freshness-dot--dated { color: var(--warning-text); }
.denden-freshness-dot--stale { color: var(--danger); }
@media (prefers-reduced-motion: no-preference) {
    @keyframes denden-claude-spin {
        to { transform: rotate(360deg); }
    }
    .denden-claude-spin {
        display: inline-block;
        animation: denden-claude-spin 1.2s linear infinite;
    }
}

/* Holds the spark line's scale, and holds both strips so a gutter can never move one
   without the other. Zero width unless a future demo has stretched the axis to full
   width: normally the strip's reserve for the "now" label leaves room to spare. */
.denden-timeline-frame {
    --denden-spark-gutter: 2.6rem;
    position: relative;
}
.denden-timeline-frame--needs-gutter {
    padding-right: var(--denden-spark-gutter);
}
.denden-timeline {
    position: relative;
    height: 32px;
    width: 100%;
    padding-right: 3rem;
}
/* The axis. Layered above the client band (z-index 0) so the line runs unbroken
   through it; every mark sits above both. Tree order alone would paint this first,
   because the band lives in the track that follows it.
   The darker tone is deliberate: as the strip's reference frame it has to stay
   readable where it crosses the band, and --main-color measured 2.86:1 there,
   under the 3:1 floor for a graphical object. This reads 8.3:1 on the band and
   12.4:1 on the page, so it improves both. */
.denden-timeline::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 0;
    right: 3rem;
    height: 2px;
    background: var(--dark-main-color);
    transform: translateY(-50%);
    z-index: 1;
    /* It paints above the band, so without this the 2px it covers would be a strip
       of dead pixels through the middle of the band's hover area (the line hit-tests
       as its parent). Nothing needs to hover the axis itself. */
    pointer-events: none;
}
.denden-timeline-track {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 3rem;
}

/* The balance line, under the strip and on the strip's x scale. The 3rem right inset
   is the strip's own (it reserves room for the "now" label), repeated here rather than
   inherited because the two are siblings: without it the spark would run 3rem wider
   than the dots it explains and every point would sit left of its cause. The
   --has-future modifier drops it for the same reason the strip does. */
.denden-timeline-spark {
    /* The plot's own right inset, matching the strip's. Named so the scale can be
       placed against the plot's edge rather than the element's: the two differ by
       exactly this, and using the element's edge put the labels a clear 3rem away from
       the axis they belong to. */
    --denden-spark-inset: 3rem;
    position: relative;
    /* Tall enough to read a shape in, and then tall enough to LABEL. At 22px the whole
       0-100% range was squeezed into a band where a council going from an even exchange
       to nothing looked like a wobble. 36px fixed the shape but not the frame: two
       10px scale labels in a 36px box sit almost on top of each other, and the three
       colours of the upright were too short to tell apart.

       The scale is deliberately the FULL range rather than fitted to each council's own
       spread, so the same height means the same thing on every page and two councils
       can be compared by eye. */
    height: 56px;
    width: 100%;
    padding-right: 3rem;
    margin-top: var(--space-3xs);
}
.denden-timeline-spark--has-future {
    --denden-spark-inset: 0rem;
    padding-right: 0;
}
.denden-timeline-spark-svg {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
}
/* The upright is the KEY: three pieces, at the heights the three bands occupy, so the
   colours on the line can be read off the chart instead of out of the help text. It is
   drawn thicker than the baseline because it is carrying meaning rather than marking a
   reference, and at 1px the three colours were hard to tell apart. */
.denden-timeline-spark-axis {
    stroke-width: 3;
}
.denden-timeline-spark-axis--one-sided { stroke: var(--danger); }
/* The palette's own yellow, chosen over an orange. It is the faintest of the three on
   white (1.63:1, against 4.5 for the red and the green), so the middle band is quieter
   than its neighbours by construction. Bernd's call, made with that trade-off in view:
   a yellow says "middling" at a glance in a way no orange does, and the middle band is
   the one a reader is least often hunting for. */
.denden-timeline-spark-axis--mixed { stroke: var(--warning); }
.denden-timeline-spark-axis--mutual { stroke: var(--success); }
/* None of them. A value, not a state, so it stays neutral: colouring it would make it
   look like a fourth band. */
.denden-timeline-spark-baseline {
    stroke: var(--muted);
    stroke-width: 1;
}
/* The scale, beside the axis it labels and outside the plot, so it never sits over the
   line. Measured from the PLOT's right edge (the element's, less the inset it holds for
   the "now" label) so the labels stay against the axis in both layouts. */
.denden-timeline-spark-scale {
    position: absolute;
    right: calc(var(--denden-spark-inset) - var(--denden-spark-gutter));
    width: var(--denden-spark-gutter);
    padding-left: var(--space-2xs);
    text-align: left;
    font-size: var(--font-badge);
    line-height: 1;
    color: var(--text-muted);
    pointer-events: none;
}
.denden-timeline-spark-scale--top { top: 0; }
.denden-timeline-spark-scale--bottom { bottom: 0; }
.denden-timeline-spark-line {
    fill: none;
    stroke-width: 2;
    stroke-linejoin: round;
    /* Round, so a council with exactly one reading renders as a dot rather than as
       nothing: its "segment" is a single point repeated. */
    stroke-linecap: round;
}
/* The hover's upright, marking the reading under the pointer. */
.denden-timeline-spark-guide {
    stroke: var(--dark-main-color);
    stroke-width: 1;
    opacity: 0.5;
}
/* And its dot. HTML rather than an SVG circle: the plot is stretched horizontally, so
   a circle in it would render as an ellipse. */
.denden-timeline-spark-dot {
    position: absolute;
    width: 7px;
    height: 7px;
    margin: -3.5px 0 0 -3.5px;
    border-radius: 50%;
    background: var(--surface-card);
    border: 2px solid var(--dark-main-color);
    pointer-events: none;
}
/* The three states. */
.denden-timeline-spark-line--one-sided { stroke: var(--danger); }
.denden-timeline-spark-line--mixed { stroke: var(--warning); }
.denden-timeline-spark-line--mutual { stroke: var(--success); }

/* The hover readout. Not a Bootstrap tooltip: that component is bound to one element
   and shows that element's title, while this one follows the pointer and changes on
   every move. It is built from the SAME tokens as `.denden-timeline-tooltip` and the
   task-note tip, so a token change still moves all three; only the properties are
   written out here, because those set Bootstrap's own variables. */
.denden-spark-tip {
    position: absolute;
    z-index: 1080;
    max-width: 320px;
    padding: 0.5rem 0.75rem;
    background: var(--surface-card);
    color: var(--text-primary);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-popover);
    font-size: var(--font-body-sm);
    line-height: 1.35;
    text-align: left;
    /* It sits under the pointer by design, so it must never take the pointer: catching
       it would fire a leave on the strip, hide the tip, and put the pointer back over
       the strip, which flickers at the refresh rate. */
    pointer-events: none;
}

/* The three lines of the hover: the day, the reading, and what it means. Stacked, so
   the figure is the thing the eye lands on rather than the middle of a sentence. */
.denden-spark-tip-date,
.denden-spark-tip-value,
.denden-spark-tip-band {
    display: block;
}
.denden-spark-tip-date {
    font-size: var(--font-body-xs);
    color: var(--text-muted);
}
.denden-spark-tip-value {
    font-weight: 600;
}
.denden-spark-tip-band {
    font-size: var(--font-body-xs);
    color: var(--text-muted);
}

/* Naming the colours in the help text, and colouring the hover's figure to match the
   stretch of line it was read off. A swatch would be another thing to explain, so the
   word itself carries the colour it names. */
.denden-spark-key { font-weight: 600; }
.denden-spark-key--one-sided { color: var(--danger); }
/* The band said in WORDS takes the paired text tone, not the yellow itself. Yellow
   text on white is 1.63:1 where text needs 4.5:1, so it would be a label nobody can
   read. The palette is built as this pair (see the contact-status and mailing pills,
   which fill with --warning and write on it with --warning-text), so the two are the
   same colour doing its two jobs rather than two colours for one band. */
.denden-spark-key--mixed { color: var(--warning-text); }
.denden-spark-key--mutual { color: var(--success); }
/* A forecast link in a council panel: the shared status badges and the open-in-new-tab
   icon, both inside one anchor.
   The padding is the point. The glyph alone measures about 16px, under the 24px hit
   area the style guide sets even for a deliberately bare icon button, and this anchor
   is now the row's ONLY control with two of them side by side. A mis-tap here opens the
   working copy instead of the approved forecast.
   The widget's own rules are written for the /forecasts/ tree, where the clusters line
   up in a column: `min-width` reserves that column, `justify-content: center` fills it,
   and the border draws the column's edge. None of that has a job beside a council name
   in a 20rem panel, where the reserved width pushed the row apart and the border read
   as a stray box around two icons. All four are neutralised here.
   Colour, glyphs and gaps are untouched, so the marks themselves are identical on both
   surfaces: this changes the container, never the vocabulary. */
.denden-forecast-link {
    padding: 0.25rem;
    line-height: 1;
    text-decoration: none;
}
.denden-forecast-link .forecast-status-combo {
    min-width: 0;
    justify-content: flex-start;
    border: 0;
    padding: 0;
}
/* The stretch this council has been subscribed. Full height of the box, because it
   is a property of a span of time rather than of the axis line, and the bottom layer
   of the strip so the axis runs through it and every mark stays the figure.
   The top and bottom hairlines carry the region: the fill alone is 1.27:1 against
   the page, so the two edges (which are the whole meaning) would otherwise be the
   least visible part of it. No left rule, by request.
   The band takes the pointer, because it carries its own tooltip (the period it
   marks, which used to be spelled out under the strip). Every mark sits above it
   at z-index 2 and keeps its own hover; the band only answers where nothing else
   is. `cursor: help` matches the dots, so the whole strip reads as hoverable. */
.denden-timeline-client-band {
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--band-left);
    width: var(--band-width);
    background: var(--light-main-color);
    border-top: 1px solid var(--main-color);
    border-bottom: 1px solid var(--main-color);
    z-index: 0;
    cursor: help;
    /* A subscription that started today renders about half a percent wide, which is
       three or four pixels: too small to hover, in exactly the case where a reader
       most wants the date. Pad the hit area without widening the painted fill. */
    min-width: 3px;
}
.denden-timeline-client-band::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 24px;
    transform: translateX(-50%);
}
/* Bound the tooltip to something visible. On a wide band the label appears at the
   band's centre, far from the pointer, and without a hover state there is nothing
   to say which region produced it. */
.denden-timeline-client-band:hover {
    border-top-width: 2px;
    border-bottom-width: 2px;
}
/* The subscription began before the first thing on the axis, so the left edge is
   where the history starts, not where the subscription did. Fading it says the
   start is off-screen rather than asserting a date we cannot place. */
.denden-timeline-client-band--open-start {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 2.5rem);
    mask-image: linear-gradient(to right, transparent 0, #000 2.5rem);
}
.denden-timeline-dot {
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--main-color);
    /* White halo lifts each mark off the axis line and keeps overlapping dots
       readable as separate marks (partial mitigation for co-located events).
       Load-bearing over the client band: the dot and the band share a hue, so this
       ring is what keeps the mark separable from its background. */
    box-shadow: 0 0 0 2px var(--text-on-dark);
    cursor: help;
    z-index: 2;
}
.denden-timeline-dot:hover {
    width: 16px;
    height: 16px;
    box-shadow: 0 0 0 2px var(--text-on-dark), var(--shadow-focus);
}
.denden-timeline--has-demos {
    --timeline-height: 52px;
    --timeline-line-y: 36px;
    height: var(--timeline-height);
}
.denden-timeline--has-demos::before,
.denden-timeline--has-demos .denden-timeline-dot,
.denden-timeline--has-demos .denden-timeline-now {
    top: var(--timeline-line-y);
}
.denden-timeline-demo {
    position: absolute;
    top: 4px;
    bottom: calc(var(--timeline-height) - var(--timeline-line-y));
    width: 13px;
    transform: translateX(-50%);
    cursor: help;
    /* Above the axis (1), so the stem meets the line instead of being cut by it. */
    z-index: 2;
}
.denden-timeline-demo::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 13px;
    bottom: 0;
    width: 2px;
    margin-left: -1px;
    background: var(--dark-complementary-color);
}
.denden-timeline-demo-square {
    position: absolute;
    top: 0;
    left: 0;
    width: 13px;
    height: 13px;
    border-radius: 2px;
    background: var(--dark-complementary-color);
    box-shadow: 0 0 0 1.5px var(--text-on-dark);
}
.denden-timeline-demo:hover .denden-timeline-demo-square {
    box-shadow: 0 0 0 1.5px var(--text-on-dark), var(--shadow-focus);
}
.denden-timeline-demo--upcoming .denden-timeline-demo-square {
    background: transparent;
    border: 2px solid var(--dark-complementary-color);
}
.denden-timeline-tooltip--demo { pointer-events: auto; }
.denden-timeline-tooltip--demo a {
    color: var(--text-on-dark);
    text-decoration: underline;
    font-weight: 600;
}
/* The row somebody's link landed on. Plain `:target`, which is all a plain link
   needs: no script sets this, the browser does.

   Scoped to `.denden-permalink-host`, NOT to `.list-group-item`. The submenu rules
   below were de-scoped deliberately, and this one was given the same treatment without
   the same reason: `.list-group-item` is Bootstrap's and is used on five non-DenDen
   templates (dataset lists, forecast lists, two tools pages), every one of which would
   have picked up a DenDen history tint the moment a fragment named one of its rows.
   The class is on every history row, so nothing is lost by keying on it.

   A left rule plus a tint, not colour alone, because the demo row already spends a
   left border on a different meaning and a reader who cannot separate the two tints
   still sees which row was meant. The rule is `--info-text`, not `--info`: `--info` on
   `--info-bg` measures 1.68:1, which is not resolvable as a shape and so was not
   actually carrying the "never colour alone" argument its comment made. */
.denden-permalink-host:target {
    background-color: var(--info-bg);
    box-shadow: inset 3px 0 0 0 var(--info-text);
}
/* Muted text on a coloured row takes `--muted-text`, per style guide §1.1. Bootstrap's
   `.text-muted` is `#6c757d !important`, which measures 4.02:1 on this tint and fails
   WCAG AA, and every history row's date and writer list uses it. The row somebody was
   sent a link to must not be the one whose detail is hardest to read. `!important` is
   required to beat the utility and is sanctioned by §1.2 for exactly this. */
.denden-permalink-host:target .text-muted {
    color: var(--muted-text) !important;
}
/* The handover trail beside the Leading control. Its own line rather than a flex item
   in the heading row: opening it made the list the tallest thing in an
   `align-items-center` row, so the Tasks heading, the status pill and the whole Leading
   control slid downwards as it expanded. The class was referenced by the template and
   defined nowhere, so none of this was happening at all. */
.denden-lead-trail {
    flex-basis: 100%;
    margin-top: var(--space-2xs);
}
/* Nested dropdowns: a `<li class="dropdown-submenu">` holding its own
   `<ul class="dropdown-menu">`, which flies out to the side of its parent.

   Bootstrap 5 does not ship nested dropdowns, so this is ours. It lived inline in
   `templates/forecast-base.html` scoped to `.forecast-nav-bar`, which made the
   Markers menu the only place in the product that could have one. It is here so a
   second surface uses the SAME submenu rather than a copy that drifts; the DenDen
   person page's "Add another detail" is the first of those. Rules are unchanged from
   the forecast nav apart from dropping that scope. `dropdown_submenu.js` adds the
   click and keyboard halves. */
.dropdown-submenu {
    position: relative;
}
.dropdown-submenu > .dropdown-menu {
    top: 0;
    left: 100%;
    margin-top: -0.25rem;
    margin-left: 0.1rem;
}
.dropdown-submenu > .dropdown-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    white-space: nowrap;
}
/* A caret pointing RIGHT, at the side it opens towards. Bootstrap's own points down,
   which on a flyout says the panel appears below it. */
.dropdown-submenu > .dropdown-toggle::after {
    border-top: 0.3em solid transparent;
    border-right: 0;
    border-bottom: 0.3em solid transparent;
    border-left: 0.3em solid;
    margin: 0;
    flex-shrink: 0;
}
/* Inside a RIGHT-ALIGNED menu, the panel opens leftwards instead.
   The forecast nav's Markers menu sits on the left of a wide bar and always had room
   to the right. An Actions menu is pinned to the right edge of the page
   (`dropdown-menu-end`), so the same rule pushed the submenu off the side of the
   window and Nickname/Pronouns were half cut off. Keyed on Bootstrap's own alignment
   class, so any right-aligned menu gets this without having to know about it. */
.dropdown-menu-end .dropdown-submenu > .dropdown-menu {
    left: auto;
    right: 100%;
    margin-left: 0;
    margin-right: 0.1rem;
}
.dropdown-submenu:hover > .dropdown-menu,
.dropdown-submenu.show > .dropdown-menu {
    display: block;
}
/* Below the point where a flyout has room beside its parent, the panel drops INTO the
   menu instead of beside it. The side placement is declared, not measured, so on a
   narrow window a right-aligned menu (which is already at the edge) pushed its submenu
   off-screen with no scroll and nothing to bring it back. Static placement cannot fall
   off anything. */
@media (max-width: 575.98px) {
    .dropdown-submenu > .dropdown-menu {
        position: static;
        left: auto;
        right: auto;
        margin: 0 0 0 var(--space-md);
        border: 0;
        box-shadow: none;
        padding-top: 0;
    }
}
/* The link-to-this-row control. Sized and coloured to sit quietly beside the row's
   own text: it is a utility somebody goes looking for, never something competing with
   the subject line for attention. It becomes legible on hover and is always legible
   to a keyboard. */
.denden-history-permalink {
    color: var(--text-muted);
    opacity: 0;
    margin-left: var(--space-2xs);
    text-decoration: none;
    /* 24px of clickable region around a 16px glyph, per style guide §2.4. The bare
       look is about visuals and does not waive the hit-area floor, and these stack a
       few pixels apart down a long thread, which is where a mis-click costs most. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 24px;
    min-height: 24px;
    vertical-align: middle;
}
/* CHILD chains, not descendant ones. Written as `.list-group-item:hover .denden-...`
   the reveal leaked to every nested permalink, so hovering anywhere in an open thread
   lit up its own icon plus one per message inside it. `denden-permalink-host` was added
   to prevent exactly that and could not, because the selector above it matched
   everything anyway. */
.denden-permalink-host:hover > summary > .denden-history-permalink,
.denden-permalink-host:hover > details > summary > .denden-history-permalink {
    opacity: 1;
}
.denden-history-permalink:focus-visible {
    opacity: 1;
    outline: none;
    box-shadow: var(--shadow-focus);
    border-radius: var(--radius-compact);
}
/* Keep it showing on the row a link just landed on, so somebody arriving at a row can
   see the handle they arrived by. */
.denden-permalink-host:target > summary > .denden-history-permalink,
.denden-permalink-host:target > details > summary > .denden-history-permalink {
    opacity: 1;
}
/* Touch has no hover, so the control would be permanently invisible and the whole
   feature unreachable on a phone. */
@media (hover: none) {
    .denden-history-permalink { opacity: 1; }
}
.denden-invite-callout {
    padding: 6px 8px;
    border-left: 3px solid var(--info);
    background: var(--info-bg);
    border-radius: 4px;
}
/* Demo rows interleaved into the council history list. Company-red is the DEMO
   IDENTITY marker (same token as the timeline demo dots), NOT an alarm. Row
   surface stays neutral; the state (held/upcoming/awaiting) rides in the pill
   using semantic tokens. The distinction is triple-coded (accent + icon + the
   word "Demo") so it never depends on colour alone. */
.denden-history-item--demo {
    border-left: 3px solid var(--dark-complementary-color);
}
.denden-history-demo-icon {
    color: var(--dark-complementary-color);
}
/* Solid company-red square marking a demo in the history list — the same shape
   and colour as the demo dots on the engagement timeline. */
.denden-history-demo-square {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 2px;
    background: var(--dark-complementary-color);
    vertical-align: middle;
}
/* Shared skeleton for the header status panels (demo, quote, any future one):
   the card supplies the surface, this supplies padding + a state accent. Demo
   and quote layer their own semantic modifiers on top. */
/* 20rem, not 18: a forecast name plus its external-link icon (eg "Weddin (New Small
   Areas Scenario)") needs the extra 2rem to stay on one line. The header caps the
   panel below this anyway on a narrow window, and the word joiner in the markup is
   what guarantees the icon never strands, whatever the width ends up being. */
.denden-status-panel-wrap {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-width: 20rem;
}
/* Bootstrap .card supplies the neutral surface, hairline border and radius; the
   accent + pill carry the state, so an invite (info) and a booking (success)
   never read as the alarm/complementary family. */
.denden-status-panel {
    padding: 8px 12px;
    border-left: 3px solid var(--info);
}
.denden-demo-panel--booked {
    border-left-color: var(--success);
}
.denden-demo-panel--awaiting {
    border-left-color: var(--warning);
}
/* A live quote reads as neutral/info; the accent turns to the warning family
   only when it is close to lapsing or has lapsed. */
.denden-quote-panel--soon,
.denden-quote-panel--expired {
    border-left-color: var(--warning);
}
/* A client's own forecasts are a settled fact, not a deadline: the accent stays
   the positive family, unlike the demo forecast panel that counts down. */
.denden-client-forecast-panel {
    border-left-color: var(--success);
}
.denden-forecast-panel--soon {
    border-left-color: var(--warning);
}
.denden-forecast-panel--overdue {
    border-left-color: var(--danger);
}
.denden-status-panel-title {
    font-weight: 600;
    font-size: var(--font-body-sm);
    color: var(--text-primary);
}
.denden-status-panel-when {
    font-size: var(--font-body-xs);
    color: var(--text-muted);
    margin-top: 4px;
}
.denden-timeline-now {
    position: absolute;
    right: 0;
    top: 50%;
    transform: translateY(-50%);
    font-size: var(--font-body-xs);
    color: var(--main-color);
    font-weight: 600;
}
/* When a future demo stretches the axis past "now", the track spans full width
   and the "now" marker floats at its proportional position (inline left %),
   so upcoming demos sit to its right instead of collapsing onto it. */
.denden-timeline--has-future {
    padding-right: 0;
}
.denden-timeline--has-future::before,
.denden-timeline--has-future .denden-timeline-track {
    right: 0;
}
.denden-timeline--has-future .denden-timeline-now {
    right: auto;
    /* Sits to the RIGHT of the moment it names rather than centred on it. Centred,
       its opaque fill erased the last few pixels of the client band, whose right
       edge is exactly "now": label ink covering data ink. The fill is the card
       token rather than a raw white (it is the same colour; the point is the token,
       not a change in appearance). */
    transform: translate(2px, -50%);
    background: var(--surface-card);
    padding: 0 2px;
    z-index: 3;
}
/* Light timeline tooltip — matches the app's help-icon popover look (white,
   hairline border, soft shadow) instead of Bootstrap's default dark tooltip. */
/* Shared light surface for DenDen hover tooltips (timeline marks, truncated task
   notes). Grouped rather than duplicated so one token change moves both. */
.tooltip.denden-timeline-tooltip,
.tooltip.denden-task-note-tooltip {
    --bs-tooltip-bg: var(--surface-card);
    --bs-tooltip-color: var(--text-primary);
    --bs-tooltip-opacity: 1;
    --bs-tooltip-max-width: 320px;
    --bs-tooltip-font-size: var(--font-body-sm);
    --bs-tooltip-border-radius: var(--radius-card);
    --bs-tooltip-padding-x: 0.75rem;
    --bs-tooltip-padding-y: 0.5rem;
}
.tooltip.denden-timeline-tooltip .tooltip-inner,
.tooltip.denden-task-note-tooltip .tooltip-inner {
    border: 1px solid var(--border-subtle);
    box-shadow: var(--shadow-popover);
    text-align: left;
}

.denden-search-item {
    white-space: normal;
}
.smart-search-results .denden-search-type-potential_client {
    background-color: var(--main-color);
}
.smart-search-results .denden-search-type-client {
    background-color: var(--success);
}
.smart-search-results .denden-search-type-mailbox {
    background-color: var(--dark-tertiary-color);
}
.smart-search-results .denden-search-type-person {
    background-color: var(--dark-main-color);
}

/* The attendee row is a flex line: name and detail, then presence, then Remove.
 *
 * Two things stop the controls forming a column. A flex item will not shrink below its
 * own min-content, and an email address is one unbreakable word, so the identity
 * column shoved everything right until Remove left the dialog entirely. And the
 * presence control sized itself to the chosen option, so picking a value moved Remove
 * sideways on that row alone. Fixing only the first still leaves the rows twitching. */
.denden-attendee-slot-identity {
    min-width: 0;
    overflow-wrap: anywhere;
    /* Wide enough to be worth reading before the controls drop below it. Below
     * Bootstrap's lg breakpoint `modal-lg` reverts to 500px, which leaves about 90px
     * beside three answers and a bin: without this the row wraps as a unit instead of
     * mincing a long address down the left edge. */
    flex: 1 1 18ch;
}
.denden-attendee-row {
    flex-wrap: wrap;
}
.denden-attendee-row .denden-attendee-presence,
.denden-attendee-row .denden-attendee-remove {
    flex-shrink: 0;
}
/* The answer segments in the conversation dialogs.
 *
 * Selection is carried by outline-versus-filled, the same channel the small-area
 * filter buttons and the competitor pills use, so it survives without colour
 * (WCAG 1.4.1). Bootstrap fills the label of a checked `btn-check` for free.
 *
 * The colour is darkened from --main-color to --dark-main-color for these groups
 * only. At the filter-button size --main-color measures 3.64:1 both filled and
 * outlined, and on a filter that costs nothing; here the same text states whether a
 * named council officer was in a room, and an operator who cannot read it cannot check
 * what they are about to save. --dark-main-color measures about 10.5:1 both ways.
 *
 * `white-space: nowrap` keeps each answer on one line so the three form a stable
 * column down the list. */
.denden-attendee-presence .control-btn,
.denden-demo-state-group .control-btn,
.denden-attendee-bulk .control-btn,
.denden-demo-choice-group .control-btn {
    white-space: nowrap;
    --bs-btn-color: var(--dark-main-color);
    --bs-btn-border-color: var(--dark-main-color);
    --bs-btn-hover-bg: var(--dark-main-color);
    --bs-btn-hover-border-color: var(--dark-main-color);
    --bs-btn-active-bg: var(--dark-main-color);
    --bs-btn-active-border-color: var(--dark-main-color);
}
/* `.control-btn` carries a translateY lift and a shadow, which is fine on a handful of
 * filter helpers and wrong on 36 of these in a scrolling dialog: the whole list twitches
 * as the pointer crosses it. The style guide bans one-off lift hovers outside filter
 * widgets, and there is no reduced-motion guard on the original rule. */
.denden-attendee-presence .control-btn:hover,
.denden-demo-state-group .control-btn:hover,
.denden-attendee-bulk .control-btn:hover,
.denden-demo-choice-group .control-btn:hover,
.denden-attendee-remove:hover {
    transform: none;
    box-shadow: none;
}

.denden-attendee-editor--locked .denden-attendee-row:not(.denden-attendee-row--new) .denden-attendee-remove {
    display: none;
}
.denden-attendee-editor--locked .denden-attendee-fix-help {
    display: none;
}
.denden-attendee-row--flash {
    background-color: var(--light-main-color);
    transition: background-color 0.3s ease-in;
}

.denden-task-meta { font-size: var(--font-body-sm); color: var(--text-muted); margin-left: 8px; }
/* `white-space: normal` is load-bearing, not tidying. The task row's action bar sets
   `nowrap` to keep its buttons on one line, and the Next dropdown is a DESCENDANT of
   that bar, so every word inside it inherited nowrap. A dropdown holds only short
   button labels most of the time, so this went unnoticed for as long as it did; the
   moment the Wait panel gained a sentence of prose, the sentence refused to wrap, the
   menu sized itself to the full 1236px of it, and the panel hung 73px off the left
   edge of the window. Any prose added to either panel depends on this line. */
.denden-next-menu { min-width: 16rem; white-space: normal; }
/* The disclosure panels inside a Next menu can be tall: the handing-over one carries a
   picker, a note, four quick buttons, a timescale pair, a date and three buttons. Without
   a cap, the part pushed off-screen is the primary button, and a menu Popper has flipped
   upward overflows past the top of the document where scrolling cannot reach it. Capped
   against the viewport rather than a fixed height so it holds at 125% and 150% zoom. */
.denden-next-panel {
    /* Wide enough for the four quick-wait buttons to sit on one row with their
       "Fill in:" label. They need 324px; the menu's own 16rem min-width leaves only
       268px, so "2 weeks" dropped to a second line on its own. The min-width lives on
       the PANEL rather than the menu so the plain list of Next choices stays narrow:
       a hidden panel is display:none and contributes no width. `min()` keeps it inside
       a narrow window, where a bare min-width would win over max-width and overflow. */
    min-width: min(20rem, calc(100vw - 2.5rem));
    max-height: min(70vh, 30rem);
    overflow-y: auto;
}

.denden-task-transitions-diagram { max-width: 100%; height: auto; cursor: zoom-in; }
.denden-prewrap { white-space: pre-wrap; }
.denden-task-notes { white-space: pre-wrap; }
.denden-task-history-current {
    padding-left: 8px;
    border-left: 3px solid var(--main-color);
    margin-left: -12px;
}
/* Muted caution line inside a fragile-rate chart tooltip (§3.10). */
.fragile-caution { opacity: 0.7; }
.denden-toast { z-index: 1080; transition: opacity 0.4s; }
.denden-toast.alert-danger { min-width: 18rem; }
.denden-lead-select { width: auto; }
.denden-council-type-select { width: auto; }
.denden-summary-clamped {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.denden-section-heading {
    font-size: var(--font-heading-md);
    font-weight: 500;
    margin-bottom: 8px;
}
.denden-subsection-heading {
    font-size: var(--font-body-sm);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    margin-top: 16px;
    margin-bottom: 4px;
    font-weight: 600;
}
.denden-pc-status {
    letter-spacing: 0.02em;
    border-radius: 999px;
}
.denden-pc-status--active { background-color: var(--success-bg); color: var(--success-text); }
.denden-pc-status--waiting { background-color: var(--warning-bg); color: var(--warning-text); }
.denden-pc-status--booked { background-color: var(--success-bg); color: var(--success-text); }
.denden-pc-status--invite { background-color: var(--info-bg); color: var(--info-text); }
.denden-pc-status--awaiting { background-color: var(--warning-bg); color: var(--warning-text); }
.denden-pc-status--quote-live { background-color: var(--info-bg); color: var(--info-text); }
.denden-pc-status--quote-expiring { background-color: var(--warning-bg); color: var(--warning-text); }
.denden-pc-status--quote-expired { background-color: var(--warning-bg); color: var(--warning-text); }
.denden-pc-status--held { background-color: var(--success-bg); color: var(--success-text); }
.denden-pc-status--upcoming { background-color: var(--info-bg); color: var(--info-text); }
.denden-pc-status--forecast-live { background-color: var(--info-bg); color: var(--info-text); }
/* Neutral, not warning. Both are SETTLED outcomes sitting in the same list as
   "Awaiting outcome", which IS a warning because somebody still has to answer it.
   Sharing the warning colour made a closed question look like an open one, and that
   matters most for "Don't know", which is what somebody answered after going over the
   record.

   `--muted-text`, not `--text-muted`: the latter reads 3.69:1 on this ground, and the
   pill is 0.6rem at weight 600, which is ordinary text by WCAG 1.4.3 and needs 4.5:1.
   This is 6.43:1 and looks no louder. One rule for both, because two identical blocks
   drift the first time either is touched. */
.denden-pc-status--did-not-happen,
.denden-pc-status--unknown { background-color: var(--light-main-color); color: var(--muted-text); }
.denden-pc-status--client { background-color: var(--success-bg); color: var(--success-text); }
/* Not an error: a scenario is meant to start private. It is a "you still have to do
   something before sending this" state, which is the warning family, not danger. */
.denden-pc-status--no-access { background-color: var(--warning-bg); color: var(--warning-text); }
/* Transfer progress: a council name repeated 150 times reads as a wall in prose, so
   each one gets a chip. The tone carries the weight of the row, which is the argument
   the page makes; it is not a status, so it borrows the semantic backgrounds rather
   than the .denden-pc-status pill shape.
   The meter is Bootstrap's .progress with a height override, the same one line
   .denden-participants-progress uses, rather than a second bar component. */
.denden-transfer-meter { height: 12px; margin-bottom: 12px; }
.denden-transfer-meter .progress-bar { background-color: var(--main-color); }
.denden-transfer-chip {
    display: inline-flex;
    align-items: baseline;
    gap: 8px;
    padding: 4px 8px;
    border-radius: var(--radius-compact);
    border: 1px solid var(--border-subtle);
    font-size: var(--font-body-sm);
    text-decoration: none;
    color: var(--text-primary);
}
/* currentColor, not the brand cyan: a backlog chip's border carries its stage, and a
   hover that repainted it would drop that signal at the moment of the click. */
a.denden-transfer-chip:hover { border-color: currentColor; text-decoration: underline; }
a.denden-transfer-chip:focus-visible { box-shadow: var(--shadow-focus); outline: none; }
/* ECharts measures its container, so the height has to come from CSS or the chart
   renders zero pixels tall. Modest, because this is one line on one page and not the
   page's subject. */
.denden-burndown-chart { height: 260px; }
/* The chip WAS the link and did not look like one, so a list of 177 things to do read
   as 177 labels and the click went undiscovered. The plus states the action, and states
   it inside the target rather than beside it, so there is still only one thing to aim
   at. Filled rather than outlined, because an outlined glyph among the outlined state
   and stage markers read as one more marker instead of a control, and because a chip
   that adds a council and a chip that opens an existing record were otherwise
   separated by nothing but a faint 15px box. No opacity: at 0.75 the plus sat at
   2.9:1 against the chip, under the 3:1 WCAG 1.4.11 floor for a control.
   Filled per tone, and the fill colours are NAMED rather than taken from
   `currentColor`: setting `background-color: currentColor` and `color` on the same
   element makes the background resolve to the colour just assigned, which painted a
   white box on a pink chip and made the plus vanish. */
.denden-transfer-chip-add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: center;
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    border-radius: var(--radius-compact);
    background-color: var(--text-muted);
    color: var(--text-on-dark);
    font-size: var(--font-badge);
    line-height: 1;
}
.denden-transfer-chip--urgent .denden-transfer-chip-add {
    background-color: var(--danger-text);
    color: var(--danger-bg);
}
.denden-transfer-chip--open .denden-transfer-chip-add {
    background-color: var(--info-text);
    color: var(--info-bg);
}
.denden-transfer-chip--urgent {
    border-color: var(--danger);
    background-color: var(--danger-bg);
    color: var(--danger-text);
}
/* Info, not success. Green is this app's done/OK family (--success-bg carries
   .denden-pc-status--active, --client, --held), and every council in this band is
   outstanding work, so green read as "nothing to do here". */
.denden-transfer-chip--open {
    border-color: var(--info);
    background-color: var(--info-bg);
    color: var(--info-text);
}
.denden-transfer-chip--muted { color: var(--text-muted); }
/* No opacity on either marker. Composited over the chip they landed at 2.7:1 to
   3.8:1 against the 4.5:1 WCAG 1.4.3 floor, and the muted tone is most of the list.
   The tokens already separate enough at full strength; the outline and the letter
   spacing do the rest of the work the alpha was doing. */
.denden-transfer-chip-state { font-size: var(--font-body-xs); color: inherit; }
/* The name and its figure are one flex item, so the chip's 8px gap falls either side
   of the pair rather than between them. As siblings they sat at the same distance as
   the state and the stage, which made the figure read as a third marker: the exact
   opposite of what the rule below says it is. */
.denden-transfer-chip-name {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
}
/* The council's own size. No outline and no uppercase: the state and the stage are
   markers you scan for, and this is part of the council's name rather than a third
   thing competing with them. Tabular figures so a wrapped run of chips does not
   jitter as the digits change width. */
.denden-transfer-chip-people {
    font-size: var(--font-body-xs);
    color: inherit;
    font-variant-numeric: tabular-nums;
}
/* The state and the marker sat side by side in one treatment and read as a single
   run of small caps ("NSW PAYING"). The marker carries its own outline so the two
   separate at a glance. */
.denden-transfer-chip-note {
    font-size: var(--font-body-xs);
    text-transform: uppercase;
    letter-spacing: 0.02em;
    padding: 0 4px;
    border: 1px solid currentColor;
    border-radius: var(--radius-compact);
}
/* Council list: every value holds one line, so a date never breaks after the month
   and a lead's surname never drops below their given name. The council column is the
   slack absorber (width:100% pushes the rest down to their natural width) and is the
   only one allowed to wrap, for the handful of very long names. The header is left
   free to wrap so "Threads (team total)" doesn't force the table wider than the
   container. */
.denden-council-table tbody td { white-space: nowrap; }
.denden-council-table tbody td:first-child { white-space: normal; width: 100%; }
/* A flex child defaults to min-width:auto, so a long council name or a wordy action
   button pushes the row wider instead of letting the description shrink.

   `clip`, not Bootstrap's `.text-truncate`, which is otherwise this rule exactly. That
   utility uses `overflow: hidden`, which makes a scroll container: focusing the note
   inside (it carries tabindex so keyboard users can reach its tooltip) scrolls the
   council name out of view with no scrollbar to bring it back. `clip` does not scroll.
   The padding and matching negative margin give the focus rings inside room to draw,
   since a ring is painted outside the inline box and would otherwise be shaved off. */
.denden-task-row-main {
    min-width: 0;
    overflow: clip;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 4px 2px;
    margin: -4px -2px;
}

/* The actions travel together, and give up no width: `flex-shrink: 0` is what makes
   that true, or flexbox shares the deficit and the one button that is allowed to wrap
   starts wrapping while the description still has room to give.

   `nowrap` sits on the group rather than on `.btn` so it reaches the plain links too
   ("History", "Open council"): a link breaking into "Open / council" is the same defect
   as "Wait 1 / week". The one button carrying `text-wrap` stays exempt through
   Bootstrap's !important, which is right, because "Reinstate previous wait until 3 Sep
   2026" is a sentence.

   Layout itself (display, alignment, gap) comes from the utility classes on the element,
   so the row's spacing and this group's cannot drift apart. */
.denden-task-actions {
    flex-shrink: 0;
    white-space: nowrap;
}

/* A standing note about our own records rather than a council needing work: the hygiene
   card and the unassigned-councils card are the same kind of thing, so they share one
   accent instead of drifting apart at the top of the same page. Neutral rather than
   warning-coloured, because a gap somebody has not filled in is not an emergency. */
.denden-standing-note { border-left: 3px solid var(--info); }

/* Deliberately NOT `.denden-task-row-main`, which clips to one line. Here the tail of
   the sentence is the date and the second question, so clipping it would take away the
   part that makes the row answerable. */
.denden-hygiene-question { min-width: 0; }
.denden-hygiene-row { flex-wrap: wrap; }

/* The invitation's own words, quoted above the fields they help you fill in. Set apart
   with a rule and muted text so it reads as something we were sent rather than
   something we wrote, and capped so a pasted dial-in block cannot push the form off
   the screen. */
.denden-invite-recap {
    /* `--muted-bg` reads 1.19:1 against the dialog, so the rule the comment above
       promises was not actually visible. `--muted` carries weight as a separator, and a
       border wants a border colour rather than a background token. */
    border-left: 3px solid var(--muted);
    padding-left: var(--space-sm);
}
/* The video service, top right of the invitation recap: the tile with its name
   underneath. Brand colours are the services' own, not our tokens. */
.denden-video-platform {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex-shrink: 0;
    gap: 0.25rem;
    margin-left: var(--space-sm);
    font-size: 0.8rem;
    font-weight: 600;
}
.denden-video-platform-tile {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 0.5rem;
    font-size: 1.25rem;
    color: #fff;
}
.denden-video-platform--zoom .denden-video-platform-tile { background: #0B5CFF; }
.denden-video-platform--teams .denden-video-platform-tile { background: #5059C9; }
.denden-video-platform--meet .denden-video-platform-tile { background: #00832D; }
.denden-invite-agenda {
    max-height: 8rem;
    overflow-y: auto;
    white-space: pre-wrap;
    /* `pre-wrap` only breaks at whitespace, and a Zoom or Teams join link is one
       unbreakable word. Nearly every invitation we hold carries one, so without this
       the agenda overran its box and, because `overflow-y` is set, the browser gave the
       panel a horizontal scrollbar at the top of the dialog. */
    overflow-wrap: anywhere;
}

/* Below tablet width the whole row would otherwise overflow the page, because the
   actions alone can exceed the viewport. Here the row wraps again and the description
   goes back to full text: two short lines beat a page that scrolls sideways. */
@media (max-width: 768px) {
    .denden-task-row { flex-wrap: wrap; }
    .denden-task-row-main {
        overflow: visible;
        text-overflow: clip;
        white-space: normal;
    }
    .denden-task-actions { margin-left: 0; }
}
/* The participant name gives way before the pills do. Capped rather than merely
   shrinkable, so a long address cannot take the whole row on a wide screen and leave the
   pills hanging off the end; `min-width: 0` is what lets a flex item shrink below its
   content at all. See the comment in _participant_row.html for what went wrong without
   it. */
.denden-participant-name {
    min-width: 0;
    max-width: 28rem;
}

.min-w-0 { min-width: 0; }

/* A KIND, not a state. States (contact status, role) own the semantic colour families;
   a kind takes neutral tokens so a mailbox never reads as a problem to fix. */
/* Padding, weight and vertical-align match .denden-role-tag and
   .denden-contact-status-pill exactly: all three sit side by side in one mailbox row,
   so any divergence lands them on different baselines. */
.denden-mailbox-pill {
    background-color: var(--light-main-color);
    color: var(--dark-main-color);
    border: 1px solid var(--border-subtle);
    letter-spacing: 0.02em;
}
.denden-mailbox-members {
    margin-top: 4px;
    padding-left: 20px;
    border-left: 2px solid var(--border-subtle);
}
.denden-task-note { cursor: help; }
.denden-quote-jump {
    display: inline-block;
    margin-top: 4px;
    text-decoration: none;
}
.denden-quote-jump:hover { text-decoration: underline; }
.denden-quote-jump:focus-visible { box-shadow: var(--shadow-focus); outline: none; }
#thread-list-container details.denden-jump-highlight {
    box-shadow: var(--shadow-highlight);
}
@media (prefers-reduced-motion: no-preference) {
    #thread-list-container details {
        transition: box-shadow 0.4s ease-out;
    }
}
.denden-task-open-badge {
    letter-spacing: 0.02em;
    border-radius: 999px;
    background-color: var(--success-bg);
    color: var(--success-text);
}
.denden-task-type-badge {
    letter-spacing: 0.02em;
    border-radius: 999px;
    background-color: var(--light-main-color);
    color: var(--dark-main-color);
}
/* Add a note (a disclosure) and Log a call (a modal trigger) sit side by side.
   When the note is opened its form drops onto its own full-width line, so the
   textarea keeps room rather than being squeezed into a flex column. */
.denden-history-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 0.5rem;
}
.denden-history-actions .denden-add-note[open] { flex-basis: 100%; }
.denden-details-summary { cursor: pointer; }
.denden-details-summary:hover { text-decoration: underline; }
.denden-details-summary:focus-visible {
    box-shadow: var(--shadow-focus);
    outline: none;
    border-radius: 4px;
}
.denden-details-summary::marker { color: var(--text-muted); }
.denden-details-summary.card-header { list-style-position: inside; }
.denden-lightbox-img { max-width: 100%; max-height: 75vh; height: auto; display: block; margin: 0 auto; }
/* A popover left open on the page beneath must not float over an open modal. */
body.modal-open .popover { z-index: 1040; }
/* Unless the modal is where it came from, in which case it has to clear the dialog
   (Bootstrap's modal is 1055) or it opens behind the thing it explains. */
body.modal-open .popover.popover-in-modal { z-index: 1065; }
.denden-card-title-width { width: 14rem; }
.denden-past-council-badge { font-size: var(--font-badge); }
.denden-participants-progress { height: 4px; }
/* The bar and its word count share a line below the pills, 3px under them.

   THREE declarations make that 3px real, and each was measured rather than reasoned:

   - `align-items: end`, so the bar's bottom edge and the count's bottom edge sit on one
     line. `center` floated the 4px bar in the middle of the count's 13.5px line box, so
     a 3px margin rendered as 7.8px of daylight and the bar read as belonging to the row
     below; `start` put the bar at the top with the digits hanging below it; `baseline`
     was worst at 10px. Bottom is the only one where the two things look like one
     measure.
   - `line-height: 1`, because the inherited 1.5 makes the row's line box 24px and the
     bar sinks accordingly.
   - The Bootstrap `d-flex align-items-center gap-2` utilities are OFF the element.
     `align-items-center` is `!important`, so with them there this rule cannot reach the
     alignment at all.

   Without any gap the last (often wrapped) pill row sits flush on the bar, and 4px of
   bar directly under a 16px pill reads as an underline on the pill rather than as a
   measure of the person. */
.denden-participant-metrics {
    display: flex;
    align-items: end;
    column-gap: 8px;
    margin-top: 3px;
    line-height: 1;
}

/* The number and its unit are two cells rather than one sentence, so every label is the
   same width and every track beside them ends in the same place. The digits are
   right-aligned, which puts the ones column under the ones column; the unit is
   left-aligned, so "words" and "meetings" start together instead of each label sitting
   centred on its own length.

   The widths are set rather than intrinsic BECAUSE the alignment has to hold ACROSS
   rows, and each row is its own element with nothing to size itself from. The value
   column's width arrives from the panel, measured from the largest figure that panel
   actually prints (`metric_value_width`); the unit column holds "meetings", the longest
   word either line can print. */
.denden-participant-metric-label {
    display: flex;
    column-gap: var(--space-2xs);
}
.denden-participant-metric-value {
    width: var(--denden-metric-value-width, 4ch);
    text-align: right;
    font-variant-numeric: tabular-nums;
}
/* Wide enough for "meetings" and no wider. It cannot size itself to its own content:
   "words" and "meetings" differ, so a content-sized unit would make the two lines'
   labels different widths and their tracks would stop ending together. */
.denden-participant-metric-unit {
    width: 4rem;
    text-align: left;
    white-space: nowrap;
}

.badge-counter {
    background-color: var(--muted);
    color: #ffffff;
    padding: 0 0.5rem 0 0;
    margin-right: 0.25rem;
    white-space: nowrap;
    display: inline-block;
}

.badge-description {
    vertical-align: middle;
    background-color: var(--light-main-color);
    color: #111111;
    padding: 0.15rem;
    white-space: nowrap;
}

.progress-bar {
    height: 15px;
}

.hidden {
    display: none !important;
}

.responsive-images img {
    max-width: 100%;
    height: auto;
}

.small-superscript-icon {
    font-size: x-small;
    vertical-align: super;
}

h1 .help_icon,
h2 .help_icon {
    vertical-align: text-top;
}

.forecast-nav-bar {
    background-color: var(--dark-main-color);
}

.forecast-nav-bar .dropdown-menu {
    background-color: var(--dark-main-color);
}

.forecast-nav-bar .dropdown-item:hover {
    background-color: var(--main-color);
    color: #ffffff;
}

.forecast-nav-bar .nav-item,
.forecast-nav-bar .dropdown-item {
    color: var(--light-main-color);
}

.link-on-dark {
    color: var(--light-main-color);
}

.link-on-dark:hover,
.link-on-dark:focus {
    color: #ffffff;
}

.footer-acknowledgement {
    max-width: 50rem;
    margin: 0 auto;
    text-align: center;
}

.grouped_dropdown_item {
    padding-left: 2.5rem !important;
}

.forecast-nav-bar .nav-link {
    color: var(--light-main-color);
}

.forecast-nav-bar .nav-link:hover,
.forecast-nav-bar .nav-link:focus,
.forecast-nav-bar .nav-link:active {
    color: #ffffff;
}

.forecast-nav-bar .forecast-selector-item {
    flex-shrink: 0;
}

.forecast-nav-bar .forecast-selector-item .form-select {
    width: 9rem;
    min-width: 0;
    text-overflow: ellipsis;
}

.forecast-nav-bar .settings-cog {
    font-size: 1.15rem;
    line-height: 1;
}

.forecast-nav-bar .settings-cog::after {
    vertical-align: 0.15em;
}

.forecast-nav-bar .icon-container {
    display: inline-flex;
    justify-content: center;
    align-items: center;
    width: 1rem;
    padding-right: 0.2rem;
}

.help_icon {
    color: var(--muted);
    cursor: pointer;
}

/* The "?" annotates the TOP of whatever it explains, never its middle.
 *
 * In a flex row the container's own `align-items` decides where a child sits, so a row
 * that centres its buttons also centred the icon, leaving it floating at the middle of a
 * tall control where it reads as a stray dot rather than an annotation. `align-self`
 * beats the container's `align-items`, so this holds whatever the row asks for and an
 * author cannot get it wrong by reaching for `align-items-center`.
 *
 * Scoped to rows holding something TALLER THAN A LINE OF TEXT, and that scope is
 * load-bearing rather than caution. Beside a single line of text, `align-items: center`
 * already puts the icon at the cap height of the letters, which is exactly where a
 * superscript belongs; forcing it to the top of the line box would lift it into the
 * leading, ~7px clear of the text it annotates. Measured on an explainer page: a 35px
 * control centres the icon 12px down (wrong, now fixed), while a 24px line of text
 * centres it 7px down (right, and left alone). So the guide's `align-items-baseline`
 * advice for a single line of text still stands and this rule does not fight it.
 *
 * Scoped to the icon element as well: the readability seal shares the `help_icon` class
 * on a `<span>` carrying a visible label, which is not an annotation on a neighbour.
 * Inline uses (an icon inside a heading) are untouched, because `align-self` means
 * nothing outside a flex container; `vertical-align` still governs those. */
.d-flex:has(> .btn, > .form-control, > .form-select, > input, > select, > textarea,
            > h1, > h2, > h3) > i.help_icon {
    align-self: flex-start;
}

.help_icon--warning {
    background-color: #fff;
    padding: 2px;
    border-radius: 50%;
}

.company-name {
    color: var(--main-color);
    font-style: italic;
    font-family: "Calibri", sans-serif;
    font-size: 1.2em;
}

.pi-spinner {
    border: 4px solid var(--light-main-color); /* Lighter shade for the spinner background */
    border-top-color: var(--main-color); /* Main color for the spinner */
    animation: spin 1s linear infinite;
}

.hide-spinner {
    display: none !important; /* Ensure this rule takes precedence */
}


@keyframes spin {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}

.vertical-align-middle th,
.vertical-align-middle td {
    vertical-align: middle !important;
}

.hover-bg-light:hover {
    background-color: var(--bs-light) !important;
}

.chart {
    height: 500px;
}

.section-content-gap {
    margin-top: var(--section-heading-gap);
}

.comparison-chart,
.combo-chart,
.multi-series-chart,
.run-time-chart {
    height: 600px;
}

.choropleth-chart {
    height: 700px;
}

ul {
    list-style-type: square;
}

ul li::marker {
    color: var(--main-color);
}

.sorting-options ul,
#area_names ul {
    column-count: 1;
}

/* Forecast tree (Reddit-style expand/collapse) */
.forecast-list-page {
    max-width: 900px;
    --forecast-status-col-width: 7.5rem;
    --forecast-action-col-width: 15.625rem;
    --forecast-col-gap: 1.5rem;
    --forecast-tree-radius: 0.25rem;
    --tree-label-width: 12rem;
}

.forecast-section-heading h2 {
    margin-bottom: 0;
}

.forecast-tree {
    list-style: none;
    padding-left: 0;
    margin: 0;
}

.forecast-tree ul {
    list-style: none;
    margin: 0;
}

.forecast-tree li {
    list-style-type: none;
}

.forecast-tree details > summary {
    list-style: none;
}

.forecast-tree details > summary::-webkit-details-marker {
    display: none;
}

.forecast-tree-children {
    position: relative;
    padding-left: var(--forecast-tree-indent);
    margin-left: 0;
    cursor: pointer;
}

.forecast-tree-children > li {
    position: relative;
    cursor: auto;
}

.forecast-tree-children > li::before {
    content: '';
    position: absolute;
    left: calc(var(--forecast-tree-chevron-half) - var(--forecast-tree-indent) - 1px);
    top: 0;
    bottom: 0;
    border-left: 2px solid var(--dark-grey-color);
    pointer-events: none;
}

.forecast-tree-children > li:first-child::before {
    top: -0.5rem;
}

.forecast-tree-children > li:last-child::before {
    bottom: auto;
    height: 1rem;
}

.forecast-tree-children > li:first-child:last-child::before {
    top: -0.5rem;
    height: 1.5rem;
}

.forecast-tree-children > li::after {
    content: '';
    position: absolute;
    left: calc(var(--forecast-tree-chevron-half) - var(--forecast-tree-indent) - 1px);
    top: 1rem;
    width: calc(var(--forecast-tree-indent) - var(--forecast-tree-chevron-half) + 0.5rem - 1px);
    border-top: 2px solid var(--dark-grey-color);
    pointer-events: none;
}

.forecast-tree-children:hover > li::before,
.forecast-tree-children:hover > li::after {
    border-color: var(--main-color);
}

.forecast-tree-summary .forecast-status-combos {
    margin-right: calc(var(--forecast-action-col-width) + var(--forecast-col-gap) + 0.5rem) !important;
    justify-content: flex-end;
}

.forecast-tree > li > details > summary .forecast-tree-label {
    flex-grow: 0;
    flex-shrink: 0;
    min-width: 0;
    width: var(--tree-label-width);
}

.forecast-tree > li > details > summary .forecast-status-combos,
.forecast-tree-children > li > details > summary .forecast-status-combos {
    margin-right: 0 !important;
    justify-content: flex-start;
}

.forecast-tree-children > li > details > summary .forecast-tree-label {
    flex-grow: 0;
    flex-shrink: 0;
    min-width: 0;
    width: calc(var(--tree-label-width) - var(--forecast-tree-indent));
}

.forecast-status-combo {
    padding: 0.125rem 0.375rem;
    border: 1px solid var(--light-grey-color);
    border-radius: var(--forecast-tree-radius);
    min-width: 5.5rem;
}

.forecast-tree-summary {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.25rem 0.5rem;
    cursor: pointer;
    border-radius: var(--forecast-tree-radius);
    transition: background-color var(--forecast-tree-transition);
    list-style: none;
}

.forecast-tree-summary::-webkit-details-marker,
.forecast-tree-summary::marker {
    display: none;
    content: '';
}

.forecast-tree-summary:hover {
    background-color: var(--light-main-color);
}

.forecast-tree-summary:focus-visible {
    box-shadow: var(--shadow-focus);
    outline: none;
}

.forecast-tree-toggle {
    display: inline-block;
    width: 0.85rem;
    text-align: center;
    color: var(--text-muted);
    transition: transform var(--forecast-tree-transition);
    flex-shrink: 0;
    font-size: 0.75rem;
    line-height: 1.5rem;
}

.forecast-tree-node[open] > .forecast-tree-summary .forecast-tree-toggle {
    transform: rotate(90deg);
}

.forecast-tree-node[open] > .forecast-tree-summary .forecast-status-combos {
    display: none !important;
}

.forecast-tree-label {
    flex-grow: 1;
}

.forecast-tree-leaf {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.25rem 0.5rem;
    border-radius: var(--forecast-tree-radius);
}

.forecast-tree-leaf:hover {
    background-color: var(--light-main-color);
}

.forecast-tree-leaf:focus-within {
    box-shadow: var(--shadow-focus);
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    .forecast-tree-summary,
    .forecast-tree-toggle,
    .area-picker-group-toggle .bi {
        transition: none;
    }
}

/* 768px matches existing tablet breakpoint used elsewhere in base.css */
@media (min-width: 768px) {
    .sorting-options ul,
    #area_names ul {
        column-count: 2;
    }
}

/* 992px matches --desktop-breakpoint variable */
@media (min-width: 992px) {
    .sorting-options ul,
    #area_names ul {
        column-count: 3;
    }
}

ol li::marker {
    color: var(--text-color);
}

.slider::-webkit-slider-thumb {
    background: var(--main-color);
}

.slider::-moz-range-thumb {
    background: var(--main-color);
}

.slider::-ms-thumb {
    background: var(--main-color);
}

-webkit-slider-thumb:active {
    background-color: var(--light-main-color);
}

-webkit-slider-thumb,
.slider:focus::-webkit-slider-thumb,
.slider:focus::-moz-range-thumb,
.slider:focus::-ms-thumb {
    box-shadow: var(--light-main-color);
}

.pc-forecast, .pc-scenario, .pc-small-area-count {
    background-color: var(--light-main-color);
}

.pc-total-price {
    font-weight: bold;
}

/* View focus switcher dropdown */
.view-focus-switcher .dropdown-item.active {
    background-color: var(--main-color);
    color: #ffffff;
}

.view-focus-switcher .dropdown-item:hover {
    background-color: var(--main-color);
    color: #ffffff;
}

/* The Focus popover and the trigger that expands to match it on open, from ONE token — the same
   pattern, and the same reason, as the Measure control's --measure-popover-width. Without the
   trigger half, the menu hangs wider than the button it drops from. */
.view-focus-switcher .dropdown-menu {
    min-width: var(--focus-popover-width);
    /* Deep enough to clear the pinned help icon outright. It sits at top: 0.5rem and is x-small, so
       it bottoms out around 1rem — exactly where a 1rem padding put the first focus, and the first
       focus is a solid blue bar whenever it is the current one, so the icon landed on the highlight
       and read as sitting inside it (Bernd, August 2026). This leaves about half a rem of air
       between the two. Composed from the spacing scale rather than a raw 1.5rem literal. */
    padding-top: calc(var(--space-md) + var(--space-xs));
}

/* Scoped through .filter-container so it outranks the Filter's own
   `.filter-container .dropdown-toggle.show { min-width: 600px }`, which matches EVERY trigger in
   the bar rather than just the Filter's. The Measure trigger's rule is specific for the same
   reason. */
.filter-container .view-focus-switcher > .dropdown-toggle.show {
    min-width: var(--focus-popover-width);
}

.view-focus-switcher-help {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    /* Without this the icon's inherited line-height makes its box tall enough to sit on the first
       focus, which is a highlighted bar whenever that focus is the current one. */
    line-height: 1;
}

/* The sticky row in the Focus menu must not wrap: "Keep focus across pages" split over two lines
   reads as two separate things, and a dropdown can simply be as wide as its widest row. Forcing the
   label to one line is what widens the menu, since a wrapping label never asks for the width. */
.view-focus-switcher .form-check-label,
.view-focus-switcher #view-focus-sticky-note {
    white-space: nowrap;
}

/* Equal width for Focus, Filter, and Explainer buttons when closed */
.filter-container .dropdown-toggle,
.view-focus-switcher > .dropdown-toggle,
.explainer-btn {
    min-width: 13rem;
    text-align: left;
}

/* Filter dropdown styles - used across different filter dropdowns */
.filter-container {
    margin-bottom: 1rem;
}

/* Dropdown menu items (active and hover states) */
.filter-container .dropdown-item.active,
.filter-container .dropdown-item:hover {
    background-color: var(--main-color);
    color: #ffffff;
}

/* Dropdown button when a filter is active */
.filter-container .dropdown-toggle.filter-active {
    background-color: var(--main-color);
    color: #ffffff;
    border-color: var(--main-color);
}

/* Dropdown button in hover and open states */
.filter-container .dropdown-toggle:hover,
.filter-container .dropdown-toggle.show {
    background-color: var(--main-color);
    color: #ffffff;
    border-color: var(--main-color);
}

/* Expand button to match dropdown width when open */
.filter-container .dropdown-toggle.show {
    min-width: 600px;
    text-align: left;
}

/* The Measure popover is far narrower than the Filter (320px, not 600px). On open the trigger expands
   to match its OWN popover width (like the Filter trigger matches its 600px popover), so the control
   box and the unfolding area line up. Both this trigger and .measure-control-menu read the single
   --measure-popover-width token so they can never drift apart. */
.filter-container .dropdown-toggle.measure-control-toggle.show {
    min-width: var(--measure-popover-width);
}

/* The Measure popover menu itself, sized from the same token as its trigger (above). The help icon
   pins to the top-right corner of the menu via a relatively-positioned anchor. */
.measure-control-menu {
    min-width: var(--measure-popover-width);
    /* Padding set HERE rather than by a `p-3` on the element, because Bootstrap's spacing utilities
       carry !important and no ordinary rule can lift the top off one. The extra half-rem on top is
       for the reason the Focus menu's is: the pinned help icon bottoms out around 1rem, which is
       exactly where a flat 1rem started the first option, and the first option is a solid blue bar
       whenever it is the current measure, so the icon sat on the highlight. Same value as the Focus
       menu, so the two popovers open the same way. */
    padding: calc(var(--space-md) + var(--space-xs)) var(--space-md) var(--space-md);
}

.measure-control-help {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    /* As on .view-focus-switcher-help, and for the same reason: without it the icon's inherited
       line-height makes its box tall enough to reach down into the first option. */
    line-height: 1;
}

.filter-container .dropdown-toggle {
    color: var(--main-color);
    border-color: var(--main-color);
    padding-top: 0.05rem;
    padding-bottom: 0.05rem;
    text-align: left;
}

/* Hide default Bootstrap dropdown chevron */
.filter-container .dropdown-toggle::after {
    display: none;
}

/* Plus/dash icon toggle for filter */
.filter-container .dropdown-toggle .toggle-icon-dash {
    display: none;
}

.filter-container .dropdown-toggle.show .toggle-icon-plus {
    display: none;
}

.filter-container .dropdown-toggle.show .toggle-icon-dash {
    display: inline;
}

/* Ensure all list items have complete borders; otherwise, when filtered, border tops and bottoms are missing */
.dataset-list .list-group-item {
    border: 1px solid rgba(0, 0, 0, .125); /* Match Bootstrap's default border style */
}

.dataset-list .list-group-item .d-flex {
    flex-wrap: nowrap; /* Prevent badges from wrapping to new line */
}

.navbar-brand .logo-navbar {
    height: 35px;
    max-width: none;
}

.forecast-nav-bar .navbar-brand .logo-navbar {
    height: 33px;
}

/* Override Bootstrap 5 primary button colors using CSS variables */
.btn-primary {
    --bs-btn-bg: var(--main-color);
    --bs-btn-border-color: var(--main-color);
    --bs-btn-hover-bg: var(--dark-main-color);
    --bs-btn-hover-border-color: var(--dark-main-color);
    --bs-btn-active-bg: var(--dark-main-color);
    --bs-btn-active-border-color: var(--dark-main-color);
    --bs-btn-disabled-bg: var(--main-color);
    --bs-btn-disabled-border-color: var(--main-color);
}

.btn-outline-primary {
    --bs-btn-color: var(--main-color);
    --bs-btn-border-color: var(--main-color);
    --bs-btn-hover-bg: var(--main-color);
    --bs-btn-hover-border-color: var(--main-color);
    --bs-btn-active-bg: var(--main-color);
    --bs-btn-active-border-color: var(--main-color);
}

/* Tonal (quiet brand) button — see the --btn-tonal-* tokens and style-guide §2.1.
   Solid, brand-coloured, but quieter than .btn-primary so several can sit on a
   workbench page without competing with the one loud primary. */
.btn-tonal {
    --bs-btn-color: var(--btn-tonal-text);
    --bs-btn-bg: var(--btn-tonal-bg);
    --bs-btn-border-color: var(--btn-tonal-bg);
    --bs-btn-hover-color: var(--btn-tonal-text);
    --bs-btn-hover-bg: var(--btn-tonal-hover-bg);
    --bs-btn-hover-border-color: var(--btn-tonal-hover-bg);
    --bs-btn-active-color: var(--btn-tonal-text);
    --bs-btn-active-bg: var(--btn-tonal-active-bg);
    --bs-btn-active-border-color: var(--btn-tonal-active-bg);
    --bs-btn-disabled-color: var(--btn-tonal-text);
    --bs-btn-disabled-bg: var(--btn-tonal-bg);
    --bs-btn-disabled-border-color: var(--btn-tonal-bg);
    --bs-btn-focus-shadow-rgb: 0, 145, 188;
}

.btn-group > .btn-tonal.dropdown-toggle-split {
    border-left-color: var(--btn-tonal-active-bg);
}

/* Override Bootstrap 5 radio button colors */
.form-check-input[type="radio"]:checked {
    background-color: var(--main-color);
    border-color: var(--main-color);
}

.form-check-input[type="radio"]:focus {
    border-color: var(--main-color);
    box-shadow: 0 0 0 0.25rem rgba(0, 145, 188, 0.25); /* main-color with opacity */
}

/* Override Bootstrap 5 checkbox colors */
.form-check-input[type="checkbox"]:checked {
    background-color: var(--main-color);
    border-color: var(--main-color);
}

.form-check-input[type="checkbox"]:focus {
    border-color: var(--main-color);
    box-shadow: 0 0 0 0.25rem rgba(0, 145, 188, 0.25); /* main-color with opacity */
}

/* Brand the native date/time picker accent (selected day, today, picker controls) */
input[type="date"],
input[type="datetime-local"],
input[type="time"],
input[type="month"],
input[type="week"] {
    accent-color: var(--main-color);
}

/* Forecast Badge - Shared base style */
.forecast-badge {
    position: absolute;
    top: 0;
    border-radius: 0 0 8px 8px !important;
    z-index: 999;
    letter-spacing: 0.5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    padding: 6px 10px !important;
    font-size: 16px !important;
    text-transform: uppercase;
}

/* Forecast Approval Badge */
.forecast-approval-badge {
    right: 20px;
}

.forecast-approval-badge.active {
    background-color: var(--light-main-color);
    color: var(--badge-active-color);
}

.forecast-approval-badge.pending {
    background-color: var(--light-main-color);
    color: var(--badge-pending-color);
}

.forecast-approval-badge i {
    font-size: 18px;
}

/* Forecast Access Badge */
.forecast-access-badge {
    right: 70px;
}

.forecast-access-badge.public {
    background-color: var(--light-main-color);
    color: var(--badge-public-color);
}

.forecast-access-badge.internal {
    background-color: var(--light-main-color);
    color: var(--badge-internal-color);
}

.forecast-access-badge i {
    font-size: 18px;
    margin-right: 2px;
}

/* Forecast Purpose Badge */
.forecast-purpose-badge {
    right: 120px;
    font-weight: bold;
    height: 30px;
    display: flex;
    align-items: center;
}

.forecast-purpose-badge.demonstration {
    background-color: var(--light-main-color);
    color: var(--badge-demo-color);
}

.forecast-purpose-badge.development {
    background-color: var(--light-main-color);
    color: var(--badge-development-color);
}

.forecast-purpose-badge.temporary {
    background-color: var(--light-main-color);
    color: var(--badge-temporary-color);
}

/* Forecast Scenario Badge */
.forecast-scenario-badge {
    right: 120px;
    text-transform: none;
    font-weight: bold;
    height: 30px;
    display: flex;
    align-items: center;
    background-color: var(--light-main-color);
    color: var(--main-color);
}

.forecast-purpose-badge ~ .forecast-scenario-badge {
    right: var(--badge-scenario-right-with-purpose);
}

.container:has(.forecast-badge) h1 {
    padding-top: 35px;
}

/* Responsive adjustments */
@media (max-width: 768px) {
    .forecast-badge {
        font-size: 12px !important;
        padding: 4px 8px !important;
    }

    .forecast-approval-badge {
        right: 10px;
    }

    .forecast-access-badge {
        right: 50px;
    }

    .forecast-purpose-badge,
    .forecast-scenario-badge {
        right: 90px;
        height: 24px;
    }

    .forecast-purpose-badge ~ .forecast-scenario-badge {
        right: var(--badge-scenario-right-with-purpose-mobile);
    }
}

/* Responsive block container for Wagtail content blocks */
.responsive-block-container {
    max-width: 100%;
}

@media (min-width: 992px) {
    :root {
        --block-spacing: 7rem;
    }

    .responsive-block-container {
        max-width: 50%;
    }

    .responsive-block-container.wide {
        max-width: 70%;
    }

    .responsive-block-container.first-block {
        max-width: 100%;
    }
}

/* Population Filter Widget Styles */
.age-grid {
    display: grid;
    grid-template-columns: repeat(10, 1fr);
    gap: 2px;
}

.age-group-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 4px;
}

.age-btn {
    padding: 2px 4px;
    font-size: 0.75rem;
    min-width: 32px;
    white-space: nowrap;
}

.age-group-btn {
    padding: 4px 8px;
    font-size: 0.75rem;
    min-width: 58px !important;
    width: auto !important;
    white-space: nowrap;
}

.age-btn.active {
    background-color: var(--main-color);
    color: white;
    border-color: var(--main-color);
}

.age-btn:hover:not(.active) {
    background-color: var(--light-main-color);
    border-color: var(--main-color);
}

#ageGrid {
    transition: opacity 0.2s;
}

#ageGrid.disabled {
    opacity: 0.5;
}

.dw-section-disabled {
    opacity: 0.5;
    pointer-events: none;
    transition: opacity 0.3s ease-in-out, filter 0.3s ease-in-out;
    filter: grayscale(30%);
}

.filter-section-year.measure-year-disabled {
    opacity: 0.5;
    pointer-events: none;
    filter: grayscale(30%);
}

.measure-year-disabled-note {
    color: var(--text-muted);
}

.filter-section-dw-occupancy,
.filter-section-dw-structure {
    transition: opacity 0.3s ease-in-out, filter 0.3s ease-in-out;
}

.control-btn-container {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 8px;
}

.control-btn {
    padding: 2px 12px;
    font-size: var(--font-control-btn);
    min-width: 40px;
    font-weight: 500;
}

.control-btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* Sticky filter checkbox styling */
#make-filter-sticky:checked {
    background-color: var(--main-color);
    border-color: var(--main-color);
}

#make-filter-sticky:focus {
    border-color: var(--main-color);
    box-shadow: 0 0 0 0.2rem rgba(0, 145, 188, 0.25);
}

/* Dwelling sticky filter checkbox styling */
#dw-make-filter-sticky:checked {
    background-color: var(--main-color);
    border-color: var(--main-color);
}

#dw-make-filter-sticky:focus {
    border-color: var(--main-color);
    box-shadow: 0 0 0 0.2rem rgba(0, 145, 188, 0.25);
}

/* Small area filter styling */
.sa-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.sa-btn,
.dw-sa-btn,
.ht-sa-btn,
.area-picker-chip {
    padding: 4px 8px;
    font-size: 0.75rem;
    white-space: nowrap;
}

.sa-btn.active,
.dw-sa-btn.active,
.ht-sa-btn.active {
    background-color: var(--main-color);
    color: white;
    border-color: var(--main-color);
}

.sa-btn:hover:not(.active),
.dw-sa-btn:hover:not(.active),
.ht-sa-btn:hover:not(.active) {
    background-color: var(--light-main-color);
    border-color: var(--main-color);
}

.sa-btn.active:hover,
.dw-sa-btn.active:hover,
.ht-sa-btn.active:hover {
    background-color: var(--dark-main-color);
    border-color: var(--dark-main-color);
}

.ht-filter-dropdown-menu {
    min-width: 520px;
    position: relative;
}

.ht-filter-help-corner {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
}

.ht-sa-specific-container {
    display: none;
}

.ht-sa-grid {
    max-height: 250px;
    overflow-y: auto;
}

.dropdown-detail-settings {
    min-width: 220px;
}

/* Small area filter map */
.sa-filter-map {
    width: 180px;
    min-width: 180px;
    height: 180px;
    border-radius: 4px;
    background: #ffffff;
}

.sa-map-area {
    fill: #ffffff;
    stroke: var(--main-color);
    stroke-width: 1;
    cursor: pointer;
    transition: fill 0.15s;
}

.sa-map-area:hover:not(.selected) {
    fill: var(--light-main-color);
}

.sa-map-area.selected:hover {
    fill: var(--dark-main-color);
}

.sa-map-area.hover:not(.selected) {
    fill: var(--light-main-color);
}

.sa-map-area.selected.hover {
    fill: var(--dark-main-color);
}

.sa-map-area.selected {
    fill: var(--main-color);
    stroke: #ffffff;
}

@media (prefers-reduced-motion: reduce) {
    .sa-map-area {
        transition: none;
    }
}

.sa-btn.sa-hover-highlight:not(.active),
.dw-sa-btn.sa-hover-highlight:not(.active) {
    background-color: var(--light-main-color);
    border-color: var(--main-color);
}

.sa-btn.active.sa-hover-highlight,
.dw-sa-btn.active.sa-hover-highlight {
    background-color: var(--dark-main-color);
    border-color: var(--dark-main-color);
}

.filter-section-small-areas {
    border-top: 1px solid #dee2e6;
    padding-top: 0.75rem;
}

/* Year filter styling */
.year-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 4px;
}

.year-btn {
    padding: 4px 6px;
    font-size: 0.75rem;
    min-width: 50px;
    white-space: nowrap;
}

.year-btn.active {
    background-color: var(--main-color);
    color: white;
    border-color: var(--main-color);
}

.year-btn:hover:not(.active) {
    background-color: var(--light-main-color);
    border-color: var(--main-color);
}

/* Dwelling year button styling */
.dw-year-btn {
    padding: 4px 6px;
    font-size: 0.75rem;
    min-width: 50px;
    white-space: nowrap;
}

.dw-year-btn.active {
    background-color: var(--main-color);
    color: white;
    border-color: var(--main-color);
}

.dw-year-btn:hover:not(.active) {
    background-color: var(--light-main-color);
    border-color: var(--main-color);
}

.year-range-container {
    margin: 8px 0;
    padding: 8px;
    background: transparent;
    border-radius: 4px;
}

.year-range-labels {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    font-weight: 500;
    margin-bottom: 4px;
}

.year-range-slider {
    position: relative;
    width: 100%;
    height: 30px;
}

.year-range-slider input[type="range"] {
    position: absolute;
    width: 100%;
    pointer-events: none;
    -webkit-appearance: none;
    background: transparent;
    height: 30px;
}

.year-range-slider input[type="range"]::-webkit-slider-thumb {
    pointer-events: auto;
    -webkit-appearance: none;
    height: 18px;
    width: 18px;
    border-radius: 50%;
    background: var(--main-color);
    cursor: pointer;
    margin-top: -6px;
}

.year-range-slider input[type="range"]::-webkit-slider-runnable-track {
    height: 5px;
    background: #ddd;
    border-radius: 3px;
}

.year-range-slider input[type="range"]:last-child::-webkit-slider-runnable-track {
    background: transparent;
}

/* "Add interval" link uses the brand main colour, not Bootstrap's default link blue. */
.period-add,
.period-add:hover,
.period-add:focus {
    color: var(--main-color);
}

/* Filter sections - stacked vertically */
.filter-sections-row {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.filter-section-age,
.filter-section-year {
    border-top: 1px solid #dee2e6;
    padding-top: 0.75rem;
}

/* Filter section fieldsets - remove default styling */
.filter-sections-row fieldset {
    border: none;
    margin: 0;
    padding: 0;
}

.filter-sections-row legend {
    float: none;
    width: auto;
    padding: 0;
    margin-bottom: 0.5rem;
}

/* Responsive filter dropdown */
@media (max-width: 768px) {
    .filter-container .dropdown-menu {
        min-width: auto !important;
        max-width: 100vw;
        width: 100vw;
        left: 0 !important;
        right: 0 !important;
        transform: none !important;
        border-radius: 0;
        margin-top: 0.5rem;
    }

    .filter-container .dropdown-toggle.show {
        min-width: auto;
    }

    .filter-sections-row {
        flex-direction: column;
    }

    .age-grid,
    .year-grid {
        grid-template-columns: repeat(5, 1fr);
    }

    .age-group-grid {
        display: flex;
        flex-wrap: wrap;
        gap: 4px;
    }
}

/* Filter button text truncation */
.filter-container .dropdown-toggle {
    max-width: 400px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Explainer button and section */
.explainer-btn {
    color: var(--main-color);
    border-color: var(--main-color);
    padding-top: 0.05rem;
    padding-bottom: 0.05rem;
}

.explainer-btn:hover {
    background-color: var(--main-color);
    color: #ffffff;
    border-color: var(--main-color);
}

.explainer-bar {
    margin-bottom: 0;
}

.explainer-bar .table-toggle-btn {
    padding-top: 0.16rem;
    padding-bottom: 0.16rem;
}

.explainer-bar .table-toggle-btn[aria-expanded="true"] {
    border-radius: 0.25rem 0 0 0.25rem;
}

.explainer-bar .chart-bar-download {
    margin: -1px -1px -1px 0;
}

[id^="explainer-"][id$="-collapse"].collapsing {
    transition: height 0.35s ease !important;
}

#view-mode, #edit-mode {
    transition: opacity 0.2s ease;
}

.explainer-panel {
    padding: 10px 0 0 0;
    margin-bottom: 3rem;
}

.explainer-panel h2 {
    font-size: 32px;
    font-weight: bold;
    line-height: 1.2;
    color: var(--text-heading-color);
}

.explainer-panel p:last-child {
    margin-bottom: 0;
}

/* Secondary nav-bar — shared by library (`.explainer-nav-bar`) and denden (`.denden-nav-bar`). */
.explainer-nav-bar,
.denden-nav-bar {
    margin-bottom: 1rem;
}
.explainer-nav-bar { background-color: var(--light-main-color); }
.denden-nav-bar    { background-color: var(--light-tertiary-color); }

.explainer-nav-bar .container,
.denden-nav-bar .container {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.explainer-nav-bar .navbar-brand,
.denden-nav-bar .navbar-brand {
    font-family: "Calibri", sans-serif;
    font-size: 2rem;
    line-height: 1;
    color: var(--dark-main-color);
}
.explainer-nav-bar .navbar-brand:hover,
.denden-nav-bar .navbar-brand:hover {
    color: var(--dark-main-color);
}

.explainer-nav-link,
.denden-nav-link {
    font-size: 0.9rem;
    font-weight: 500;
    padding: 0.25rem 0;
}
.explainer-nav-link { color: var(--dark-main-color) !important; }
.explainer-nav-link:hover { color: var(--main-color) !important; }
.denden-nav-link { color: var(--text-color) !important; }
.denden-nav-link:hover { color: var(--text-color) !important; text-decoration: underline; }

/* DenDen navbar dropdowns: the same shape as the forecast navbar dropdown, but in
   DenDen's own pink, a pink menu with dark text and a stronger-pink active/hover row.
   No persistent underline and no heavy focus box on the toggle. */
.denden-nav-bar .dropdown-menu {
    background-color: var(--light-tertiary-color);
    border: none;
    box-shadow: var(--shadow-popover);
    padding: 0.25rem 0;
}
.denden-nav-bar .dropdown-item { color: var(--text-color); }
.denden-nav-bar .dropdown-item:hover,
.denden-nav-bar .dropdown-item:focus,
.denden-nav-bar .dropdown-item.active {
    background-color: var(--dark-tertiary-color);
    color: var(--text-color);
}
/* The current page carries a non-colour cue on top of the background, so it stays
   distinguishable once another item is hovered (hover shares the same pink). */
.denden-nav-bar .dropdown-item.active { font-weight: 600; }
/* The toggle keeps the sibling hover underline (`.denden-nav-link:hover`), and no
   heavy Bootstrap focus box on mouse click or while open, but a keyboard
   `:focus-visible` still gets a visible ring so it's never left bare. */
.denden-nav-bar .dropdown-toggle:focus,
.denden-nav-bar .dropdown-toggle.show {
    box-shadow: none;
    outline: none;
    text-decoration: none;
}
.denden-nav-bar .dropdown-toggle:focus-visible {
    box-shadow: var(--shadow-focus);
    outline: none;
}

.explainer-nav-bar button.dropdown-toggle {
    background: none;
    border: none;
}
.explainer-nav-bar .dropdown-item.active {
    background-color: var(--light-main-color);
    color: var(--dark-main-color);
}
.explainer-nav-bar .small-superscript-icon {
    vertical-align: baseline;
    align-self: flex-start;
    display: inline-flex;
    margin-top: 0;
}

.explainer-type-select {
    width: auto;
    font-size: 0.85rem;
}

.explainer-library-list {
    list-style: none;
    padding-left: 0;
    margin-bottom: 0;
}

/* Type-ahead search box: `shared_elements/smart_search_widget.html` for the markup,
   `js/search/smart_search.js` for the wiring. ONE class family serves every consumer (the
   explainer navbar, the DenDen navbar, an ordinary form field). Each rule used to name
   both consumers in its selector list, so a third box meant editing ten rules; add a
   `--modifier` here instead, and only where the surroundings genuinely demand it. */
.smart-search-wrapper {
    flex: 1 1 auto;
}

/* The compact pill that sits in a nav bar beside links, rather than in a form. */
.smart-search-wrapper--navbar {
    min-width: 140px;
    max-width: 220px;
    width: 220px;
}

.smart-search-wrapper--navbar .smart-search-input {
    font-size: var(--font-body-sm);
}

.smart-search-results {
    position: absolute;
    top: 100%;
    left: 0;
    right: auto;
    width: 100%;
    max-width: calc(100vw - 2rem);
    max-height: 420px;
    overflow-y: auto;
    padding: 0.25rem 0;
    display: none;
    list-style: none;
    margin: 0.125rem 0 0 0;
    z-index: 1050;
}

/* A form-field box takes its own field's width (the base rule above). A navbar pill is
   220px, which is far too narrow to read a result in, so that one case widens instead. */
.smart-search-wrapper--navbar .smart-search-results {
    width: 480px;
}

/* Hangs the dropdown off the right edge, for a box at the right-hand end of a nav bar
   where a 480px panel opening leftwards would run off the viewport. */
.smart-search-results--right { left: auto; right: 0; }

.smart-search-results.show {
    display: block;
}

.smart-search-results li {
    padding: 0;
}

.smart-search-results .dropdown-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    white-space: normal;
}

/* The default renderer's text column: name on the first line, subtitle under it. The
   min-width lets it shrink, so an unbreakable email address cannot shove the badge out
   of the row (the same trap the attendee row hit). */
.smart-search-text {
    flex: 1 1 auto;
    min-width: 0;
}

/* One name rule for every consumer. `flex` applies when the span is a direct child of
   the flex `.dropdown-item` (the explainer and DenDen rows); it is inert inside
   `.smart-search-text`, where `display: block` is what stacks name over subtitle. */
.smart-search-name,
.smart-search-subtitle {
    flex: 1 1 auto;
    min-width: 0;
    display: block;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* `--muted-text`, not Bootstrap's `.text-muted`: the latter is #6c757d, which reads
   3.69:1 against the `--light-main-color` highlight the top row always carries, and a
   subtitle is ordinary text by WCAG 1.4.3 and needs 4.5:1. This is 6.43:1. Same call,
   and the same two tokens, as the DenDen status pills further up this file. */
.smart-search-subtitle {
    color: var(--muted-text);
}

.smart-search-results--denden .smart-search-name {
    font-weight: 500;
}

.smart-search-results .badge {
    flex: 0 0 auto;
    font-size: var(--font-badge);
    color: var(--text-on-dark);
}


.smart-search-results li.highlighted .dropdown-item {
    background-color: var(--light-main-color);
    color: var(--dark-main-color);
    border-left: 3px solid var(--dark-main-color);
}
/* DenDen's search dropdown lives in the pink navbar, so its keyboard-highlighted row
   wears DenDen pink (matching the People/Funnel nav dropdowns and the leaked
   dropdown-item :hover), not the shared blue above. The left bar stays as the
   keyboard-vs-hover cue, recoloured off blue to DenDen's complementary accent. The
   blue arm above still applies to every other box, the explainer navbar included. */
.smart-search-results--denden li.highlighted .dropdown-item {
    background-color: var(--dark-tertiary-color);
    color: var(--text-color);
    border-left: 3px solid var(--dark-complementary-color);
}
/* On the pink ground the muted subtitle colour is 3.05:1, which still fails WCAG 1.4.3,
   so the highlighted row's subtitle takes the row's own colour (7.84:1) instead. The
   unhighlighted rows keep the muted colour on the white dropdown. */
.smart-search-results--denden li.highlighted .smart-search-subtitle {
    color: var(--text-color);
}

/* The funnel-metrics activity-calendar person filter is a body dropdown; give its
   selected/hovered row DenDen's pink instead of Bootstrap's default blue. */
.dropdown-menu[aria-labelledby="activity-calendar-person"] .dropdown-item.active,
.dropdown-menu[aria-labelledby="activity-calendar-person"] .dropdown-item:active,
.dropdown-menu[aria-labelledby="activity-calendar-person"] .dropdown-item:hover,
.dropdown-menu[aria-labelledby="activity-calendar-person"] .dropdown-item:focus {
    background-color: var(--dark-tertiary-color);
    color: var(--text-color);
}
/* A tick on the CURRENT selection, so "already picked" reads by shape, not the pink
   alone (hover/focus share that pink). Reserved (hidden) on the others to keep labels
   aligned. */
.denden-cal-tick { visibility: hidden; margin-right: 0.35rem; }
.dropdown-item.active .denden-cal-tick { visibility: visible; }

[href="#explainer-backlinks"]:focus-visible {
    outline: 2px solid var(--dark-main-color);
    outline-offset: 2px;
}

.readability-seal {
    position: absolute;
    bottom: 0.5rem;
    right: 0.5rem;
}

/* ========================================
   Demographic Table Styles
   ======================================== */

/* Section Bar Component */
.section-bar {
    display: flex;
    border: 1px solid var(--main-color);
    border-radius: 0.25rem;
    background-color: #ffffff;
    width: fit-content;
    transition: background-color 0.15s ease-in-out;
}

.section-bar:has(.table-toggle-btn[aria-expanded="true"]) {
    width: auto;
}

/* When the toggle button is blue (expanded or hovered), the bar's own background goes blue too.
   The bar and its blue children share the same 0.25rem corner radius, so the children's rounded
   corners cannot fill the bar's corners exactly and a sliver of the bar's background survives
   there; on a white bar that sliver reads as white crescent marks. Matching the background makes
   the sliver invisible. The transition above matches Bootstrap's .btn background transition so
   bar and button recolour in step. */
.section-bar:has(.table-toggle-btn[aria-expanded="true"]),
.section-bar:has(.table-toggle-btn:hover) {
    background-color: var(--main-color);
}

.table-toggle-btn {
    flex: 1;
    color: var(--main-color);
    border: none;
    border-radius: 0.25rem;
    padding-top: 0.05rem;
    padding-bottom: 0.05rem;
    text-align: left;
    background-color: #ffffff;
}

.table-toggle-btn:hover {
    background-color: var(--main-color);
    color: #ffffff;
}

.table-toggle-btn[aria-expanded="true"] {
    background-color: var(--main-color);
    color: #ffffff;
    border-radius: 0.25rem 0 0 0.25rem;
}

/* Plus/dash icon toggle for table bar */
.table-toggle-btn .toggle-icon-dash {
    display: none;
}

.table-toggle-btn[aria-expanded="true"] .toggle-icon-plus {
    display: none;
}

.table-toggle-btn[aria-expanded="true"] .toggle-icon-dash {
    display: inline;
}

.section-bar .dropdown,
.section-bar .table-bar-copy,
.section-bar .table-bar-download-dropdown {
    display: none;
}

.table-toggle-btn[aria-expanded="true"] ~ .dropdown,
.table-toggle-btn[aria-expanded="true"] ~ .table-bar-copy,
.table-toggle-btn[aria-expanded="true"] ~ .table-bar-download-dropdown {
    display: block;
}

.table-bar-settings {
    background-color: var(--main-color);
    color: #ffffff;
    border: none !important;
    border-radius: 0;
    padding: 0.05rem 0.5rem;
    outline: none !important;
    box-shadow: none !important;
}

.table-bar-settings:hover,
.table-bar-settings:focus,
.table-bar-settings:active,
.table-bar-settings.show,
.section-bar .dropdown.show .table-bar-settings {
    background-color: var(--main-color);
    color: #ffffff;
    opacity: 0.85;
    border: none !important;
    outline: none !important;
    box-shadow: none !important;
}

.section-bar .dropdown {
    position: relative;
    background-color: var(--main-color);
}

.section-bar .dropdown-menu {
    z-index: 1050;
}

.dropdown-menu-narrow {
    min-width: auto;
}

.input-narrow-numeric {
    width: 5em;
}

/* What a shut section is holding, eg "· 6 areas". Shares .top-n-badge's rule outright rather than
   restating it a size out of step: the first cut set 0.85em against that one's 0.75em while its own
   comment claimed the two matched.
   KNOWN AND ACCEPTED (Bernd, August 2026): on an engaged bar this sits white on --main-color, which
   measures 3.63:1, under the 4.5:1 ordinary text owes. The same pairing already ships on
   .table-toggle-btn:hover, on [aria-expanded="true"] and on .filter-active; what is new here is
   that engaged-and-collapsed is a RESTING state rather than one you pass through. Recorded rather
   than silently inherited, so the choice is visible to whoever reads this next. */
.top-n-badge,
.section-bar-badge {
    font-size: 0.75em;
    font-weight: 600;
    margin-left: 0.15em;
}

/* Only while the section is SHUT. Open, the section says its own count a row or two below, and a bar
   repeating it is the same "says the same word twice" problem that took the count off the picker's
   forecast rows. The button's own aria-expanded is the state, so no JS keeps this in step. */
.table-toggle-btn[aria-expanded="true"] .section-bar-badge {
    display: none;
}

/* The sticky note stays in the accessibility tree while empty so that filling it announces (see
   syncKeptAreasNote), which means it must take no space until it has something to say. */
#area-picker-sticky-note:empty {
    margin-bottom: 0;
}

/* The "kept across pages" pin, hard against the RIGHT edge of whatever carries it (Bernd, August
   2026). Filled, not outline: at 0.875rem an outline pin's strokes read like the chevron's.

   POSITIONED, not flexed. The first cut made the host a flex container so `margin-left: auto` could
   push the mark over, and that quietly broke two things on every Focus, Measure and Filter trigger
   in the app, because the mark renders on all of them (merely hidden) and so `:has()` matched
   everywhere:
     - `text-overflow: ellipsis` applies to BLOCK containers only, so long filter labels stopped
       showing "…" and hard-clipped mid-character instead;
     - the anonymous text flex item would not shrink (`min-width: auto`), so `margin-left: auto`
       resolved to zero and the mark was pushed past the 400px clip, invisible in exactly the state
       it exists to report.
   Taking the mark out of flow keeps the host a block container, so the ellipsis works as it always
   did, and the reserved padding below stops the mark ever sitting on the text. */
.control-kept-mark {
    position: absolute;
    right: var(--space-xs);
    top: 50%;
    transform: translateY(-50%);
    line-height: 1;
}

/* Only where a mark is actually SHOWN: an unmarked control keeps its original box untouched, and no
   room is reserved for a mark nobody can see.

   `!important` on the padding, reluctantly and for a specific reason: these triggers carry Bootstrap's
   `px-2` utility, and every Bootstrap spacing utility is declared !important, so no ordinary rule can
   widen one side of them. Without it the reserved gap silently stayed at px-2's 0.5rem and the label
   ran under the mark — 16px of overlap on a short label, 57px on a long one. The alternative is
   stripping `px-2` from five templates and restating both sides here, which puts the horizontal
   padding of every control bar in the app behind this one rule. */
.filter-container .dropdown-toggle:has(.control-kept-mark:not(.d-none)),
.table-toggle-btn:has(.control-kept-mark:not(.d-none)) {
    position: relative;
    padding-right: 1.6rem !important;
}

/* The mark stays on show while the control is OPEN as well as shut (Bernd, August 2026). Hiding it
   on open was tidier and wrong: ticking the box is done with the control open, so the mark that
   confirms the tick was invisible at the one moment it had something to say, and it only appeared
   once the reader had closed the control and looked away. Immediate feedback beats the tidiness. */

/* A section holding something reads as engaged, the same way a Filter trigger with a filter on it
   does: blue is "this control is shaping what you see". Without it a folded picker holding six areas
   looked identical to an empty one, which is the state the fold was meant to make legible. The bar
   and the button are painted together for the reason the expanded rule above gives: their shared
   corner radius leaves a sliver of bar showing, and a white sliver on a blue button reads as a
   crescent mark. */
.section-bar.section-bar-engaged,
.section-bar.section-bar-engaged .table-toggle-btn {
    background-color: var(--main-color);
    color: var(--text-on-dark);
}

/* An engaged bar was already painted in the hover colour, so hovering it changed nothing and the
   only affordance saying "this is pressable" was gone — on the one control that is now the way back
   into a folded picker. One rung darker on hover, on the bar as well as the button, because their
   shared corner radius leaves a sliver of bar showing at the corners. */
.section-bar.section-bar-engaged:has(.table-toggle-btn:hover),
.section-bar.section-bar-engaged .table-toggle-btn:hover {
    background-color: var(--dark-main-color);
}

/* The fold makes this button the required route back into the picker, and it had no visible focus
   at all: it carries no .btn-* variant, so Bootstrap's --bs-btn-focus-shadow-rgb is undefined, the
   rgba() is invalid at computed-value time and the box-shadow falls back to none while its
   `outline: 0` still applies. Same failure already recorded for .area-picker-group-toggle.
   The engaged state needs its own ring: --shadow-focus is 25%-alpha teal, invisible on teal. */
.table-toggle-btn:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

.section-bar-engaged .table-toggle-btn:focus-visible {
    box-shadow: 0 0 0 0.25rem rgba(255, 255, 255, 0.8);
}

/* Focus is moved here programmatically after Apply, and after the paused-focus banner is dismissed,
   so a screen reader lands on the result rather than at the top of the document. NO ring: the h1
   carries tabindex="-1", so it is never in the tab order and nobody can arrive here by tabbing. The
   only way to see this focus is to have just clicked a button, where a box drawn around the heading
   is noise rather than orientation (Bernd, August 2026). The outline reset covers the browsers that
   draw one of their own for programmatic focus. */
   Scoped to the tabindex, never a bare `h1:focus`: unscoped it stripped the ring from every h1 in
   both apps, so the day one becomes tab-reachable its focus goes silently invisible. Note that
   `:focus:not(:focus-visible)` does NOT do this job — on a freshly loaded document there is no
   recorded keyboard interaction for the heuristic to read, so it would suppress the ring for the
   keyboard user too. */
h1[tabindex="-1"]:focus {
    outline: none;
}

.section-bar .table-bar-download-dropdown .table-bar-download {
    border-radius: 0 0.25rem 0.25rem 0;
}

.section-bar .table-bar-download-dropdown .dropdown-menu {
    padding: 0.5rem 0;
}

.section-bar .table-bar-download-dropdown .dropdown-item {
    color: var(--main-color);
}

.section-bar .table-bar-download-dropdown .dropdown-item:hover,
.section-bar .table-bar-download-dropdown .dropdown-item:focus {
    background-color: var(--main-color);
    color: #ffffff;
}

.settings-sections {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.settings-section + .settings-section {
    border-top: 1px solid #dee2e6;
    padding-top: 0.75rem;
}

.collapsing:not([id^="explainer-"]) {
    transition: none !important;
}

.section-bar.copy-flash {
    animation: copy-flash 0.3s ease;
}

@keyframes copy-flash {
    0% { opacity: 1; }
    50% { opacity: 0.5; }
    100% { opacity: 1; }
}

.comparison-sections {
    display: flex;
    flex-direction: column;
    gap: 3rem;
}

.chart-help-icon {
    float: right;
    margin-right: 0.25rem;
    position: relative;
    z-index: 10;
}

.section-bar .chart-bar-download {
    display: none;
}

.table-toggle-btn[aria-expanded="true"] ~ .chart-bar-download {
    display: block;
}

.chart-bar-download {
    background-color: var(--main-color);
    color: #ffffff;
    border: none !important;
    border-radius: 0 0.25rem 0.25rem 0;
    padding: 0.05rem 0.5rem;
    outline: none !important;
    box-shadow: none !important;
}

.chart-bar-download:hover {
    opacity: 0.85;
}

.section-bar .table-bar-download-dropdown .dropdown-item.active,
.section-bar .table-bar-download-dropdown .dropdown-item.active:hover,
.section-bar .table-bar-download-dropdown .dropdown-item.active:focus {
    background-color: var(--main-color);
    color: #ffffff;
}

/* ========================================
   New Table Container - Sticky Headers & Columns
   ======================================== */

/* Column snapping, so a frozen column never leaves a half-covered value on screen. See
   initColumnSnapping in demographic_table_sort.js, which measures the frozen block and sets
   scroll-padding-left to it, and marks the scrollable columns .snap-col. Without this, a value
   sliding under the frozen column loses its leading digits and still reads as a valid number. */
.new-table-container.snap-columns {
    scroll-snap-type: x mandatory;
}

.new-table-container.snap-columns thead th.snap-col {
    scroll-snap-align: start;
}

/* Only the LEFT inset is applied, by initColumnSnapping, so every rest position clears the frozen
   block. scroll-padding-right was tried and rejected: it insets the snapport rather than adding room
   after the content, so a large value collapses the snapport and switches snapping off entirely. */

.new-table-container {
    max-height: calc(100vh - 250px);
    overflow: auto;
    border: 1px solid #e0e0e0;
    width: fit-content;
    max-width: 100%;
}

.new-table-container table {
    border-collapse: separate;
    border-spacing: 0;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    font-size: 14px;
    margin-bottom: 0;
    --bs-table-striped-bg: #fafafa;
}

.new-table-container table th,
.new-table-container table td {
    padding: 10px 14px;
    border: none;
    border-bottom: 1px solid #e0e0e0;
    vertical-align: middle;
}

.new-table-container table thead th {
    font-weight: 700;
    color: #333;
    background-color: #fff;
    position: sticky;
    top: 0;
    z-index: 2;
    white-space: nowrap;
    border-bottom: 2px solid #e0e0e0;
    box-shadow: none !important;
}

.new-table-container table thead th .th-label {
    display: block;
    width: min-content;
    min-width: 100%;
    white-space: normal;
    overflow-wrap: normal;
    word-break: normal;
}

/* django_tables2 wraps sortable column headers in <a>. Inherit the header's
   dark colour rather than the global link teal so the header stays readable. */
.new-table-container table thead th a {
    color: inherit;
    text-decoration: none;
}

.new-table-container table thead th a:hover {
    color: var(--main-color);
}

.new-table-container table tbody tr:hover {
    background-color: #f0f0f0;
}

.new-table-container table td {
    color: #333;
}

/* Sticky first column */
.new-table-container table td:first-child,
.new-table-container table thead th:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    white-space: nowrap;
}

/* Border on first column only for single-column sticky tables (no .sticky-col class) */
.new-table-container table td:first-child:not(.sticky-col),
.new-table-container table thead th:first-child:not(.sticky-col) {
    border-right: 2px solid #e0e0e0;
}

.new-table-container table thead th:first-child {
    z-index: 3;
    background-color: #fff !important;
    color: #333 !important;
    box-shadow: none !important;
}

/* First column sortable header needs extra padding for sort indicator + border */
.new-table-container table thead th:first-child.sortable {
    padding-right: 26px;
}

.new-table-container table tbody td:first-child {
    background-color: #fafafa !important;
    color: #333 !important;
    box-shadow: none !important;
}

.new-table-container table tbody tr:nth-child(even) td:first-child {
    background-color: #fff !important;
}

/* Override table-primary in new-table-container to use alternating colors */
.new-table-container table tbody td.table-primary {
    background-color: #fff !important;
    --bs-table-bg-state: transparent !important;
    box-shadow: none !important;
}

.new-table-container table tbody tr:nth-child(odd) td.table-primary {
    background-color: #fafafa !important;
}

/* Multiple sticky columns support */
.new-table-container table td.sticky-col,
.new-table-container table th.sticky-col {
    position: sticky;
    z-index: 1;
}

.new-table-container table thead th.sticky-col {
    z-index: 3;
    background-color: #fff !important;
    color: #333 !important;
}

.new-table-container table tbody td.sticky-col {
    background-color: #fafafa !important;
}

.new-table-container table tbody tr:nth-child(even) td.sticky-col {
    background-color: #fff !important;
}

/* Override table-primary in sticky columns to use alternating colors */
.new-table-container table tbody td.sticky-col.table-primary {
    background-color: #fafafa !important;
    box-shadow: none !important;
}

.new-table-container table tbody tr:nth-child(even) td.sticky-col.table-primary {
    background-color: #fff !important;
    box-shadow: none !important;
}

/* Border on last sticky column */
.new-table-container table td.sticky-col-last,
.new-table-container table th.sticky-col-last {
    border-right: 2px solid #e0e0e0;
}

/* Row-total ("Total") column on marker comparison tables: an aggregate, so
   rendered bold like the footer Total row. Stickiness/background comes from
   .sticky-col (applied by initStickyColumns); right-alignment from .text-end. */
.new-table-container table th.marker-total-col,
.new-table-container table td.marker-total-col {
    font-weight: 600;
}

/* Hover highlighting for sticky columns - must match specificity of nth-child rules */
.new-table-container table tbody tr:hover td.sticky-col,
.new-table-container table tbody tr:hover td:first-child,
.new-table-container table tbody tr:hover td.table-primary,
.new-table-container table tbody tr:nth-child(odd):hover td.sticky-col,
.new-table-container table tbody tr:nth-child(even):hover td.sticky-col,
.new-table-container table tbody tr:nth-child(odd):hover td.sticky-col.table-primary,
.new-table-container table tbody tr:nth-child(even):hover td.sticky-col.table-primary {
    background-color: #f0f0f0 !important;
    box-shadow: none !important;
}

/* Table responsive override */
.new-table-container .table-container.table-responsive {
    overflow: visible;
}

/* Compact Table Mode */
.new-table-container.compact-table table {
    font-size: 12px;
}

.new-table-container.compact-table table th,
.new-table-container.compact-table table td {
    padding: 4px 8px;
    line-height: 1.2;
}

.new-table-container.compact-table table thead th.sortable {
    padding-right: 16px;
}

.new-table-container.compact-table table tbody td:not(:first-child) {
    padding-right: 16px;
}

.new-table-container.compact-table table thead th.sortable .sort-indicator {
    right: 3px;
    font-size: 10px;
}

/* Forced setting indicator */
.forced-setting-note {
    font-size: 0.85em;
    color: #6c757d;
    font-style: italic;
}

/* Column and Row Highlighting */
.new-table-container table thead th.column-highlight {
    background-color: var(--table-hover-grey) !important;
}

.new-table-container table tbody td:first-child.row-header-highlight,
.new-table-container table tbody tr:nth-child(even) td:first-child.row-header-highlight {
    background-color: var(--table-hover-grey) !important;
}

/* Sortable Column Headers */
.new-table-container table thead th.sortable {
    cursor: pointer;
    user-select: none;
    padding-right: 22px;
}

.new-table-container table tbody td:not(:first-child) {
    padding-right: 22px;
}

.new-table-container table thead th.sortable:hover {
    background-color: var(--table-hover-grey);
}

/* Sort indicator icon */
.new-table-container table thead th.sortable .sort-indicator {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 12px;
    color: var(--muted);
}

/* Highlight icon when sorted */
.new-table-container table thead th.sort-asc .sort-indicator,
.new-table-container table thead th.sort-desc .sort-indicator {
    color: var(--main-color);
    font-size: 14px;
    right: 4px;
}

.new-table-container.compact-table table thead th.sort-asc .sort-indicator,
.new-table-container.compact-table table thead th.sort-desc .sort-indicator {
    font-size: 12px;
    right: 0;
}

/* The sort control is a real <button> inside the header (sortButtonFor in
   demographic_table_sort.js), so the <th> keeps its columnheader role and its aria-sort. The ring goes
   on the button, using the shared focus token (§4.1). The header's own `box-shadow: none !important`
   is what previously forced an outline here; scoping the token to the button steps around it. */
.new-table-container table thead th.sortable > .th-sort-button:focus-visible,
.sortable-table thead th.sortable > .th-sort-button:focus-visible {
    box-shadow: var(--shadow-focus);
    /* The solid outline carries the ring; the token is the halo around it, not the whole indicator.
       --shadow-focus is rgba(0, 145, 188, 0.25), which composites over the white header to about
       #BFE3EE — roughly 1.37:1, well under the 3:1 WCAG 1.4.11 and 2.4.11 ask for. Suppressing the
       outline and leaving the shadow alone traded a passing indicator (--main-color at 3.64:1) for a
       failing one, on the single control a keyboard reader uses to sort a 36-column header row. */
    outline: 2px solid var(--main-color);
    outline-offset: -2px;
    border-radius: var(--radius-compact);
}

/* That button is stripped back to the header's own typography and alignment: text-align: inherit is
   what keeps a numeric header right-aligned. */
.new-table-container table thead th.sortable > .th-sort-button,
.sortable-table thead th.sortable > .th-sort-button {
    display: block;
    width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: inherit;
    cursor: pointer;
}

/* Lightweight sortable plain table (admin/CRM lists) — reuses the shared
   demographic_table_sort.js header sorter WITHOUT the .new-table-container
   data-grid chrome (scroll pane, fit-content width, sticky columns). See
   style-guide §2.3. */
.sortable-table thead th.sortable {
    cursor: pointer;
    user-select: none;
    position: relative;
    padding-right: 22px;
}

.sortable-table thead th.sortable:hover {
    background-color: var(--table-hover-grey);
}

.sortable-table thead th.sortable .sort-indicator {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 12px;
    color: var(--muted);
}

.sortable-table thead th.sort-asc .sort-indicator,
.sortable-table thead th.sort-desc .sort-indicator {
    color: var(--main-color);
    font-size: 14px;
    right: 4px;
}

/* ========================================
   Table Header with Accent Bar
   ======================================== */

.table-header {
    margin-top: 0;
    margin-bottom: 1rem;
}

/* Chart heading rendered as HTML (Option 1): the .table-header sits flush above the chart canvas,
   replacing the old canvas-drawn title so the chart and table headings render identically.
   NOTE: a literal 0.5rem, NOT var(--block-spacing) — that token is the marketing content-block gap
   (7rem at >=992px), which here injects a ~126px gap between the heading and the chart. */
.chart-html-heading .table-header {
    margin-bottom: 0.5rem;
}

.table-header-line {
    display: flex;
    align-items: stretch;
}

.table-header-bar {
    width: var(--heading-bar-width);
    background-color: var(--main-color);
    flex-shrink: 0;
}

.table-header-title {
    min-height: var(--heading-title-line-height);
}

.table-header-title .table-header-text {
    font-size: var(--heading-title-font-size);
    font-weight: bold;
    line-height: 1.2;
    color: var(--text-heading-color);
    padding-left: var(--heading-title-gap);
    letter-spacing: var(--table-header-title-letter-spacing);
}

/* min-height, NOT height: a long subtitle (eg the period list) wraps, and a fixed one-line box
   would let the second line paint over the source line below. Matches the title's min-height. */
.table-header-subtitle {
    min-height: var(--heading-subtitle-line-height);
}

.table-header-subtitle .table-header-text {
    font-size: var(--heading-subtitle-font-size);
    font-weight: bold;
    line-height: var(--heading-subtitle-line-height);
    color: var(--text-heading-color);
    padding-left: var(--heading-subtitle-gap);
    letter-spacing: var(--table-header-subtitle-letter-spacing);
}

.table-header-source {
    height: var(--heading-source-line-height);
}

.table-header-source .table-header-text {
    font-size: var(--heading-source-font-size);
    color: var(--light-grey-color);
    line-height: var(--heading-source-line-height);
    padding-left: var(--heading-source-gap);
    letter-spacing: var(--table-header-source-letter-spacing);
}

/* Table Download Area */
.table-download-area {
    background-color: #ffffff;
}

/* ========================================
   Pricing Tier Cards
   ======================================== */

.pricing-tiers {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
    max-width: 1000px;
    margin: 0 auto;
}

@media (min-width: 768px) {
    .pricing-tiers {
        flex-direction: row;
        justify-content: center;
    }
}

.pricing-card {
    flex: 1;
    max-width: 320px;
    border: 1px solid #e0e0e0;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    background-color: #ffffff;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
}

@media (min-width: 768px) {
    .pricing-card {
        margin: 0;
    }
}

.pricing-card-header {
    background-color: var(--main-color);
    color: #ffffff;
    padding: 1rem;
    text-align: center;
}

.pricing-card-header h3 {
    margin: 0;
    font-size: 1.5rem;
    font-weight: 600;
}

.pricing-card-body {
    padding: 1.5rem;
    text-align: center;
    display: flex;
    flex-direction: column;
    height: 100%;
}

.pricing-card-features {
    list-style: none;
    padding: 0;
    margin: 0 0 1.5rem 0;
    text-align: left;
    flex-grow: 1;
}

.pricing-card-features li {
    padding: 0.5rem 0 0.5rem 1.75rem;
    position: relative;
    color: #333;
}

.pricing-card-features li::before {
    content: "\2713";
    position: absolute;
    left: 0;
    color: var(--main-color);
    font-weight: bold;
}

.pricing-card-price {
    font-size: 2.25rem;
    font-weight: 700;
    color: var(--dark-main-color);
    margin-bottom: 0.5rem;
}

.pricing-card-note {
    font-size: 0.875rem;
    color: #666;
}

/* ========================================
   Pricing Table (Single Card with Price Columns)
   ======================================== */

.pricing-table {
    max-width: 800px;
    margin: 0 auto;
    border: 1px solid #e0e0e0;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    background-color: #ffffff;
}

.pricing-table-header {
    background-color: var(--main-color);
    color: #ffffff;
    padding: 1.25rem 1.5rem;
    text-align: center;
}

.pricing-table-header h3 {
    margin: 0;
    font-size: 1.5rem;
    font-weight: 600;
}

.pricing-table-subtitle {
    margin-top: 0.25rem;
    font-size: 1rem;
    opacity: 0.9;
}

.pricing-table-body {
    padding: 1.5rem;
}

.pricing-table-features {
    list-style: none;
    padding: 0;
    margin: 0 0 1.5rem 0;
}

.pricing-table-features li {
    padding: 0.5rem 0 0.5rem 1.75rem;
    position: relative;
    color: #333;
}

.pricing-table-features li::before {
    content: "\2713";
    position: absolute;
    left: 0;
    color: var(--main-color);
    font-weight: bold;
}

.pricing-table-prices {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    margin-bottom: 1.5rem;
}

@media (min-width: 576px) {
    .pricing-table-prices {
        flex-direction: row;
        justify-content: center;
        gap: 2rem;
    }
}

.pricing-table-price-column {
    flex: 1;
    text-align: center;
    padding: 1rem;
    background-color: #f8f9fa;
    border-radius: 8px;
    max-width: 200px;
    margin: 0 auto;
}

@media (min-width: 576px) {
    .pricing-table-price-column {
        margin: 0;
    }
}

.pricing-table-price-amount {
    font-size: 1.75rem;
    font-weight: 700;
    color: var(--dark-main-color);
    margin-bottom: 0.25rem;
}

.pricing-table-price-label {
    font-size: 0.875rem;
    color: #666;
}

.pricing-table-note {
    text-align: center;
    font-size: 0.875rem;
    color: #666;
    padding-top: 1rem;
    border-top: 1px solid #e0e0e0;
}

/* ========================================
   Table provenance footnote (collapsible)
   ======================================== */

details.marker-table-notes > summary {
    cursor: pointer;
    user-select: none;
    color: var(--main-color);
}

details.marker-table-notes > summary:hover {
    text-decoration: underline;
}

details.marker-table-notes[open] > summary {
    margin-bottom: 0.25rem;
}

/* ========================================
   Explainer Contextual Links
   ======================================== */

a.explainer-link {
    text-decoration: underline dotted;
    text-decoration-color: var(--main-color);
    text-underline-offset: 3px;
    cursor: pointer;
}

.explainer-popover-content {
    font-size: 0.9rem;
}

.explainer-popover-content p {
    margin-bottom: 0.25rem;
}

.explainer-popover-readmore {
    color: var(--main-color);
}

.explainer-popover-datapage {
    display: block;
    margin-top: 0.4rem;
    padding-top: 0.4rem;
    border-top: 1px solid #dee2e6;
    color: var(--main-color);
}

.explainer-related-pages {
    margin-top: 1.5rem;
    padding-top: 1rem;
    border-top: 1px solid #dee2e6;
}

.explainer-related-heading {
    font-size: 1rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
}

.explainer-related-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    list-style: none;
    padding: 0;
    margin: 0;
}

.explainer-related-link {
    color: var(--main-color);
}

/* Pagination: tint active page link with brand colour */
.pagination .page-link {
    color: var(--main-color);
}

.pagination .page-item.active .page-link {
    background-color: var(--main-color);
    border-color: var(--main-color);
    color: var(--text-on-dark);
}

.pagination .page-link:focus {
    box-shadow: var(--shadow-focus);
}

.gmail-draft-preview {
    width: 100%;
    min-height: 14rem;
    background-color: var(--text-on-dark);
}

.dp-inline-number {
    width: 7rem;
}

/* DenDen — Connect an AI onboarding page */
.denden-connect-step {
    max-width: 62ch;
}

.denden-connection-status {
    display: inline-block;
    padding: 1px 8px;
    font-size: var(--font-body-xs);
    letter-spacing: 0.02em;
    border-radius: 999px;
    background-color: var(--success-bg);
    color: var(--success-text);
    font-weight: 600;
}

.denden-prompt-block {
    margin: 0.6rem 0 1.1rem;
}

.denden-code {
    display: block;
    margin: 0;
    padding: 0.85rem 1rem;
    background: var(--diverging-neutral-color);
    border: 1px solid var(--light-grey-color);
    border-radius: 0.5rem;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--font-body-sm);
    color: var(--text-color);
    white-space: pre-wrap;
    word-break: break-word;
    overflow-x: auto;
}

.denden-copy-btn {
    margin-top: 0.4rem;
}

/* The change-table marks (§3.1/§3.10): ※ = an undefined rate (no value), * = a fragile rate shown with
   caution. Both carry a hover popover, so a help cursor signals there is a reason to read on hover. */
.measure-change-suppressed,
.measure-change-caution {
    cursor: help;
}

/* The fragile-rate value and its * live in one wrapper so the NUMBER right-aligns with every other cell
   (all values line up on their last character), while the * hangs just to the right of that alignment
   line — positioned absolutely so it never shifts the number out of the column. */
.measure-change-value {
    position: relative;
    display: inline-block;
}
.measure-change-caution {
    position: absolute;
    left: 100%;
    padding-left: 0.25rem;
}

/* Indent the change-table caption so it lines up under the table body text rather than sitting flush
   against the left border. */
.measure-change-caption {
    padding-left: 0.75rem;
}

/* A help popover taller than the viewport opens clipped with no way to scroll it, so
   its last paragraphs are simply unreachable. Measured at 1,890px in a 1,323px window
   on the funnel metrics page. Capping the body and letting it scroll is the general
   fix; trimming individual popovers is the per-case one. */
.popover-body {
    max-height: min(70vh, 40rem);
    overflow-y: auto;
    /* macOS overlay scrollbars are invisible until you scroll, so without this the
       popover simply ends mid-paragraph with nothing saying there is more. */
    scrollbar-width: thin;
    /* A wheel gesture inside the popover must not scroll the page behind it once the
       popover has reached its end. */
    overscroll-behavior-y: contain;
}

.popover-body::-webkit-scrollbar {
    width: 0.5rem;
}

.popover-body::-webkit-scrollbar-thumb {
    background-color: var(--border-color);
    border-radius: 0.25rem;
}

/* The step-durations charts are as tall as their council count, so the taller ones
   scroll inside this wrapper. It exists so the cap does NOT land on the card body,
   which would take the heading and the Table button with it.

   Deliberately NOT `overscroll-behavior-y: contain`. That was here to stop a wheel
   gesture chaining onward, but the panel is now nearly 3,000px, so a reader scrolling
   the page with the pointer over the centre column was trapped for the whole of it and
   then had to move the pointer into the margin to carry on. Chaining is the lesser
   annoyance: the page keeps moving once the chart bottoms out. */
.denden-step-scroll {
    scrollbar-width: thin;
}

.denden-step-scroll::-webkit-scrollbar {
    width: 0.5rem;
}

.denden-step-scroll::-webkit-scrollbar-thumb {
    background-color: var(--border-color);
    border-radius: 0.25rem;
}

/* An empty category reads as unavailable but stays operable: aria-disabled marks it, and it keeps
   pointer events and its place in the tab order so the reason (announced into the status line beside
   it) is reachable by mouse, touch and keyboard alike. --text-disabled, not --muted: the label still
   has to be readable, and #BBBBBB on white is 1.9:1. */
/* Bootstrap's .disabled only drops opacity, which turns the teal outline a washed-out lavender rather
   than reading as unavailable. Greyed properly instead, and still operable so the reason (announced
   into the status line beside it) is reachable by mouse, touch and keyboard alike. */
#area-picker-categories .area-picker-category[aria-disabled="true"] {
    color: var(--text-disabled);
    border-color: var(--border-subtle);
    background-color: transparent;
    opacity: 1;
    cursor: default;
}

#area-picker-categories .area-picker-category[aria-disabled="true"]:hover {
    background-color: transparent;
    border-color: var(--border-subtle);
    transform: none;
    box-shadow: none;
}

/* The box now holds the root control as well as the tree, so the border moved out here from
   .area-picker-groups. The tree keeps the scrolling, so the root control stays put while the list
   moves under it. */
.area-picker-list {
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-compact);
    padding: var(--space-xs);
}

/* Hard left, so the root control sits outside every row's indent rather than in the column the row
   controls occupy. Position says what it acts on: the whole tree, not a branch inside it. */
.area-picker-list-header {
    display: flex;
    justify-content: flex-start;
    margin-bottom: var(--space-xs);
}

.area-picker-list-header .area-picker-branch-select {
    margin-left: 0;
}

/* Two rungs down from --space-sm (Bernd, August 2026): the rows carry smaller type and smaller branch
   buttons now, so the old gap read as loose against them. Still a gap rather than none, because a
   forecast's opened small areas have to group visibly with their own row, and the child rows sit at a
   tighter pitch than this so the fold still reads as one block. */
.area-picker-groups {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    max-height: 20rem;
    overflow-y: auto;
}

.area-picker-group-legend {
    float: none;
    width: auto;
    margin-bottom: var(--space-2xs);
    color: var(--text-heading-color);
}

/* The local government area's own row IS the group heading, with its small areas folded underneath.
   The caret, the indent and the guide line are the tree view's (see .forecast-tree-*), so the two
   places that show this hierarchy read the same. */
/* flex-start, not baseline: a long council name wraps to a second line, and on baseline the caret
   rode down to the last line instead of staying with the name it opens. */
.area-picker-group-header {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2xs);
}

/* Both get the 24px floor, or a forecast with no small areas indents its checkbox 4px less than its
   neighbours and the spacer defeats its own purpose (§2.4). */
.area-picker-group-toggle,
.area-picker-group-toggle-spacer {
    flex-shrink: 0;
    min-width: 1.5rem;
    padding: 0;
    text-align: center;
    line-height: 1.5rem;
}

.area-picker-group-toggle {
    color: var(--text-muted);
    border: 0;
    background: none;
    min-height: 1.5rem;
}

.area-picker-group-toggle:hover {
    color: var(--main-color);
}

/* Bootstrap's .btn:focus-visible resolves --bs-btn-focus-shadow-rgb from the .btn-* variant, and this
   button deliberately has none, so the shadow evaluated to nothing and the sole disclosure control on
   ninety rows had no focus ring at all (§4.1). Same treatment the tree view gives its own summary. */
.area-picker-group-toggle:focus-visible,
.area-picker-branch-select:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

.area-picker-group-toggle .bi {
    display: inline-block;
    font-size: 0.75rem;
    transition: transform var(--forecast-tree-transition);
}

.area-picker-group-toggle[aria-expanded="true"] .bi {
    transform: rotate(90deg);
}

/* The tree view's own tokens rather than two literals that happen to add up to the same 2.25rem, so
   the two places that draw this hierarchy really do stay in step (§1.2). */
.area-picker-group-children {
    position: relative;
    margin-left: var(--forecast-tree-chevron-half);
    padding-left: calc(var(--forecast-tree-indent) - var(--forecast-tree-chevron-half));
    border-left: 2px solid var(--dark-grey-color);
}

.area-picker-group:hover > .area-picker-group-children {
    border-left-color: var(--main-color);
}

/* One control per branch, root included, so a single symbol means a single thing: take this branch's
   own children. Right-aligned on the row, so they line up in a column the eye can follow down. */
/* Sized to the category pills it sits under, not to Bootstrap's btn-sm: the icon and the count were
   inheriting a larger size and the buttons read as the heaviest thing in a list of small type. */
.area-picker-branch-select {
    flex-shrink: 0;
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    padding: 2px var(--space-2xs);
    font-size: var(--font-control-btn);
    line-height: 1.4;
}

.area-picker-branch-select-count {
    font-size: inherit;
    font-variant-numeric: tabular-nums;
}

/* Kept focusable and clickable when it cannot act, like the empty category buttons: Bootstrap's
   .disabled sets pointer-events:none and blurs a focused element, which puts the reason out of reach
   of every input method. An alpha wash put the white count at ~2.1:1 over the composited grey, and
   the count is the number the refusal message refers to, so it is muted with real tokens instead. */
.area-picker-branch-select-inert,
.area-picker-branch-select-inert:hover {
    opacity: 1;
    background-color: var(--light-grey-color);
    border-color: var(--border-subtle);
    color: var(--text-disabled);
}

.area-picker-child-count {
    white-space: nowrap;
    font-size: var(--font-body-sm);
}

/* Non-breaking spaces: a plain space inside content is collapsed away, which ran the middot into
   the count as "·7 small areas". */
.area-picker-child-count::before {
    content: '\00A0\00B7\00A0';
}

.area-picker-option .form-check-label {
    font-size: var(--font-body-sm);
}

/* The pill rides on every row of a long scrolling list here, so .type-tag's 0.5rem (9px) is too
   small to read. --font-badge is the token for exactly this. */
.area-picker-type-pill {
    vertical-align: middle;
    font-size: var(--font-badge);
}

/* The forecast's own status badges, inline beside a name. Same two colour families the badges use
   (§4.5's standing contrast exception covers those tokens), so one state reads the same whether the
   reader is on the forecast or picking it out of a list. Sized off the badge font rather than the
   row's, so a mark never sets the row height. */
.area-picker-status-mark {
    display: inline-flex;
    align-items: center;
    margin-left: var(--space-2xs);
    font-size: var(--font-badge);
    line-height: 1;
    vertical-align: middle;
}

.area-picker-status-mark-main {
    color: var(--badge-mark-main);
}

.area-picker-status-mark-complementary {
    color: var(--badge-mark-complementary);
}

/* DEV / DEMO / TEMP are word marks on the badges too, so they keep the badge's uppercase letter-spaced
   treatment rather than reading as ordinary text that happens to be shouting. */
.area-picker-status-mark-word,
.comparison-mark-word {
    font-weight: 600;
    letter-spacing: 0.5px;
}

/* The same word mark inside the chart's HTML tooltip. Sized down against the row text, matching what
   the canvas rich-text run does on the plot edge and in the legend. */
.comparison-mark-word {
    font-size: var(--font-badge);
}

/* The chips fill whatever height the list beside them has, instead of stopping at a fixed 12rem and
   leaving dead space under Apply. `overflow-y: auto` on the chips is what lets the flex child shrink
   below its content: without an overflow value the automatic minimum size holds it open and the box
   grows past the column instead of scrolling. */
.area-picker-selection-column {
    display: flex;
    flex-direction: column;
}

.area-picker-chips {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: var(--space-2xs);
    flex: 1 1 auto;
    min-height: 4rem;
    overflow-y: auto;
}

/* Stacked, so the column has no height of its own to fill and the chips would stretch to nothing. */
@media (max-width: 991.98px) {
    .area-picker-chips {
        flex: 0 1 auto;
        max-height: 12rem;
    }
}

/* Same look as the small-area buttons in the filter (.sa-btn): white, grey border, dark text. Sizing
   and typography come from the shared rule those buttons already use, so the two surfaces cannot drift. */
.area-picker-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    padding-right: var(--space-2xs);
    color: var(--muted-text);
    background-color: var(--surface-card);
    border: 1px solid var(--btn-outline-secondary-border);
    border-radius: var(--bs-border-radius-sm, 0.25rem);
    /* A chip names one area in full, and the longest of those is an activity-centre small area inside
       a scenario, which runs past 110 characters. `.btn` contributes `white-space: nowrap`, so without
       this the chip simply ran out of its column (measured 651px inside 515px even before the state
       suffix was added). Wrap the text instead of clipping it: the label is the only thing telling two
       chips apart, so no part of it is spare. */
    max-width: 100%;
    white-space: normal;
    text-align: left;
}

/* The X stays on the first line beside the start of the label rather than centring itself against a
   three-line block, and never shrinks below its 24px hit area (§2.4). */
.area-picker-chip:has(> span) {
    align-items: flex-start;
}

.area-picker-chip > span {
    min-width: 0;
    overflow-wrap: anywhere;
}

.area-picker-pending {
    color: var(--muted-text);
}

.area-picker-chip-remove {
    color: var(--muted-text);
    line-height: 1;
    text-decoration: none;
}

.area-picker-chip-remove:hover {
    color: var(--dark-main-color);
}

/* A tick box is a flex item next to a forecast name that wraps over two or three lines. Without
   this it gets squeezed into an oval and each row's box lands at a different x, so the column of
   tick boxes stops being a column. The run order number sits AFTER the box for the same reason:
   anything before it pushes it right by a varying amount. */
.list-group-item > input[name="forecasts"] {
    flex-shrink: 0;
}

/* The slot keeps its width whether or not a number is in it, so ticking a forecast never reflows
   the row and pushes the name into a second line. */
.run-order {
    position: relative;
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    justify-content: center;
    min-width: 2.25rem;
}

.run-order-empty {
    visibility: hidden;
}

button.run-order-number {
    align-items: center;
    border: none;
    cursor: pointer;
    display: inline-flex;
    justify-content: center;
    min-height: 24px;
    min-width: 2rem;
}

button.run-order-number:focus-visible {
    box-shadow: var(--shadow-focus);
    outline: none;
}

/* Floated over the row rather than laid out in it, so opening the editor does not squeeze the
   forecast name while it is open. The hint sits UNDER the controls so the eye (and the pointer)
   travels straight from the box to Apply instead of past a sentence. */
.run-order-editor {
    position: absolute;
    top: calc(100% + 0.25rem);
    left: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2xs);
    padding: 0.5rem;
    white-space: nowrap;
    background-color: var(--surface-card);
    border: 1px solid var(--light-main-color);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-popover);
}

.run-order-editor-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.run-order-input {
    width: 4.5rem;
}

.run-order-confirm {
    flex-shrink: 0;
}
