/* ==========================================================================
   utilities.css — small utility classes that replace inline style overrides
   --------------------------------------------------------------------------
   The sanctioned alternative to inline `style="font-size:…"`. Each class maps
   to the --fs-* token scale (tokens.css), so a font-size tweak stays on-scale
   and on-token instead of becoming a one-off literal in markup.

   Named by px (.fs-12 … .fs-16) to avoid colliding with Bootstrap's heading
   scale (.fs-1 … .fs-6, which are large display sizes). `!important` matches
   Bootstrap utility behaviour so a utility reliably wins where it replaces an
   inline style.

   Floors (see Docs/CSS_ARCHITECTURE.md): content text ≥ 13px (.fs-13);
   decorative micro-labels ≥ 12px (.fs-12). Do NOT add an .fs-11 / sub-12 class.

   Loaded after the component layer, before public-editorial + the runtime block.
   ========================================================================== */

.fs-12 { font-size: var(--fs-12) !important; }  /* 12px — decorative micro-label floor */
.fs-13 { font-size: var(--fs-13) !important; }  /* 13px — content floor */
.fs-14 { font-size: var(--fs-14) !important; }  /* 14px */
.fs-15 { font-size: var(--fs-15) !important; }  /* 15px */
.fs-16 { font-size: var(--fs-16) !important; }  /* 16px — base */
.fs-17 { font-size: var(--fs-17) !important; }  /* 17px — editorial body lead */
.fs-18 { font-size: var(--fs-18) !important; }  /* 18px */
.fs-20 { font-size: var(--fs-20) !important; }  /* 20px */
.fs-24 { font-size: var(--fs-24) !important; }  /* 24px */
.fs-30 { font-size: var(--fs-30) !important; }  /* 30px */
.fs-36 { font-size: var(--fs-36) !important; }  /* 36px — top of the house scale */

/* Display steps. Above the house scale: the public portal's editorial lockup, and
   large decorative glyphs in the back office (empty-state icons, status symbols).
   Never for back-office text — see tokens.css. */
.fs-28 { font-size: var(--fs-28) !important; }  /* 28px */
.fs-32 { font-size: var(--fs-32) !important; }  /* 32px */
.fs-40 { font-size: var(--fs-40) !important; }  /* 40px */
.fs-64 { font-size: var(--fs-64) !important; }  /* 64px */
.fs-96 { font-size: var(--fs-96) !important; }  /* 96px — hero numeral */
.fs-hero-fluid { font-size: var(--fs-hero-fluid) !important; }

/* Line height. Bootstrap ships .lh-1 / .lh-sm / .lh-base (1.5) / .lh-lg — .lh-base
   is reused as-is; these are the steps it does not cover. */
.lh-120 { line-height: 1.2 !important; }
.lh-160 { line-height: 1.6 !important; }
.lh-170 { line-height: 1.7 !important; }
.lh-180 { line-height: 1.8 !important; }

/* --------------------------------------------------------------------------
   Full-width mobile buttons: the responsive views use `d-grid d-sm-block` (or
   d-md-block) directly on icon+label buttons/anchors to stretch them on small
   screens. Plain CSS grid treats the <i> and the text run as two grid items —
   icon on row 1, label on row 2. Flow the implicit items on one centred row
   so the pattern renders as intended everywhere it's already used.
   -------------------------------------------------------------------------- */
.btn.d-grid {
    grid-auto-flow: column;
    justify-content: center;
    align-items: center;
}

/* --------------------------------------------------------------------------
   Responsive helpers Bootstrap doesn't ship but the responsive views already
   reference (Committee Members/Details header groups, Ordinance/Resolution
   History sidebars). Defined here so the markup works as written.
   -------------------------------------------------------------------------- */
@media (min-width: 768px) {
    .w-md-auto { width: auto !important; }
    .border-md-bottom-0 { border-bottom: 0 !important; }
    .border-md-end { border-right: var(--bs-border-width, 1px) var(--bs-border-style, solid) var(--bs-border-color, #dee2e6) !important; }
}

/* ==========================================================================
   Inline-style replacements (Phase 2, tech-debt plan 2026-09-03)
   --------------------------------------------------------------------------
   These exist so markup can stop carrying style="...". The goal is dropping
   'unsafe-inline' from the CSP style-src directive, which only becomes
   possible once the LAST inline style attribute is gone - partial progress
   buys nothing, so the ladder is deliberately complete for the properties it
   covers rather than pretty.

   Note that CSSOM assignment (el.style.width = '...') is NOT affected by
   style-src at all; only style attributes in the served HTML are. So runtime
   JS sizing keeps working untouched.
   ========================================================================== */
/* Visibility
   NOT !important, and deliberately so. Bootstrap's .d-none is display:none
   !important, which would permanently beat a later el.style.display = 'block'.
   25 of the elements this class replaces are toggled exactly that way, so an
   !important here would silently break them. Inline style must still win.
*/
.is-hidden        { display: none; }

/* A validation summary with nothing to report. The tag helper renders the container on every
   page (styled as an alert); hide it until the server or jquery-validation marks it
   .validation-summary-errors, so a valid form never shows an empty red bar. */
.validation-summary-valid { display: none !important; }

/* Text flow */
.ws-nowrap        { white-space: nowrap !important; }
.ws-pre-line      { white-space: pre-line !important; }
.ws-pre-wrap      { white-space: pre-wrap !important; }

/* Overflow */
.o-hidden         { overflow: hidden !important; }
.oy-auto          { overflow-y: auto !important; }

/* Cursor */
.cursor-pointer   { cursor: pointer !important; }
.cursor-zoom-in   { cursor: zoom-in !important; }

/* Letter spacing */
.ls-04            { letter-spacing: .04em !important; }
.ls-05            { letter-spacing: .05em !important; }
.ls-06            { letter-spacing: .06em !important; }
.ls-50            { letter-spacing: .5em !important; }

/* Object fit */
.of-contain       { object-fit: contain !important; }
.of-cover         { object-fit: cover !important; }

/* Sizing
   Unit is part of the name: .h-100px is 100 pixels, never to be confused with
   Bootstrap's .h-100 (100%). Values Bootstrap already covers (w-100, w-auto,
   mw-100, h-100 ...) reuse Bootstrap's class and are NOT redefined here.
*/
.h-6px            { height: 6px !important; }
.h-10px           { height: 10px !important; }
.h-20px           { height: 20px !important; }
.h-24px           { height: 24px !important; }
.h-25px           { height: 25px !important; }
.h-30px           { height: 30px !important; }
.h-32px           { height: 32px !important; }
.h-34px           { height: 34px !important; }
.h-40px           { height: 40px !important; }
.h-46px           { height: 46px !important; }
.h-48px           { height: 48px !important; }
.h-50px           { height: 50px !important; }
.h-52px           { height: 52px !important; }
.h-60px           { height: 60px !important; }
.h-64px           { height: 64px !important; }
.h-80px           { height: 80px !important; }
.h-96px           { height: 96px !important; }
.h-100px          { height: 100px !important; }
.h-150px          { height: 150px !important; }
.h-180px          { height: 180px !important; }
.h-2rem           { height: 2rem !important; }
.h-3rem           { height: 3rem !important; }
.maxh-64px        { max-height: 64px !important; }
.maxh-100px       { max-height: 100px !important; }
.maxh-180px       { max-height: 180px !important; }
.maxh-220px       { max-height: 220px !important; }
.maxh-250px       { max-height: 250px !important; }
.maxh-300px       { max-height: 300px !important; }
.maxh-400px       { max-height: 400px !important; }
.maxh-460px       { max-height: 460px !important; }
.maxh-500px       { max-height: 500px !important; }
.maxh-600px       { max-height: 600px !important; }
.maxh-620px       { max-height: 620px !important; }
.maxw-95px        { max-width: 95px !important; }
.maxw-160px       { max-width: 160px !important; }
.maxw-180px       { max-width: 180px !important; }
.maxw-220px       { max-width: 220px !important; }
.maxw-260px       { max-width: 260px !important; }
.maxw-300px       { max-width: 300px !important; }
.maxw-400px       { max-width: 400px !important; }
.maxw-420px       { max-width: 420px !important; }
.maxw-460px       { max-width: 460px !important; }
.maxw-520px       { max-width: 520px !important; }
.maxw-620px       { max-width: 620px !important; }
.maxw-780px       { max-width: 780px !important; }
.maxw-960px       { max-width: 960px !important; }
.maxw-30vw        { max-width: 30vw !important; }
.minh-16px        { min-height: 16px !important; }
.minh-100px       { min-height: 100px !important; }
.minh-300px       { min-height: 300px !important; }
.minh-500px       { min-height: 500px !important; }
.minh-520px       { min-height: 520px !important; }
.minw-0           { min-width: 0 !important; }
.minw-28px        { min-width: 28px !important; }
.minw-36px        { min-width: 36px !important; }
.minw-70px        { min-width: 70px !important; }
.minw-120px       { min-width: 120px !important; }
.minw-150px       { min-width: 150px !important; }
.minw-240px       { min-width: 240px !important; }
.minw-700px       { min-width: 700px !important; }
.w-6pct           { width: 6% !important; }
.w-8pct           { width: 8% !important; }
.w-9pct           { width: 9% !important; }
.w-10pct          { width: 10% !important; }
.w-12pct          { width: 12% !important; }
.w-13pct          { width: 13% !important; }
.w-14pct          { width: 14% !important; }
.w-15pct          { width: 15% !important; }
.w-16pct          { width: 16% !important; }
.w-18pct          { width: 18% !important; }
.w-20pct          { width: 20% !important; }
.w-32pct          { width: 32% !important; }
.w-40pct          { width: 40% !important; }
.w-14_28pct       { width: 14.28% !important; }
.w-2px            { width: 2px !important; }
.w-14px           { width: 14px !important; }
.w-28px           { width: 28px !important; }
.w-32px           { width: 32px !important; }
.w-34px           { width: 34px !important; }
.w-36px           { width: 36px !important; }
.w-40px           { width: 40px !important; }
.w-46px           { width: 46px !important; }
.w-48px           { width: 48px !important; }
.w-50px           { width: 50px !important; }
.w-52px           { width: 52px !important; }
.w-60px           { width: 60px !important; }
.w-64px           { width: 64px !important; }
.w-80px           { width: 80px !important; }
.w-90px           { width: 90px !important; }
.w-96px           { width: 96px !important; }
.w-100px          { width: 100px !important; }
.w-120px          { width: 120px !important; }
.w-160px          { width: 160px !important; }
.w-200px          { width: 200px !important; }
.w-280px          { width: 280px !important; }
.w-350px          { width: 350px !important; }
.w-2rem           { width: 2rem !important; }
.w-3rem           { width: 3rem !important; }
.w-2_5rem         { width: 2.5rem !important; }
.h-2_5rem         { height: 2.5rem !important; }

/* JS-driven progress fill
   Deliberately NOT !important. The width here only seeds the bar at 0; the
   upload script then drives it with el.style.width = pct + '%'. An !important
   sizing utility would pin the bar at its seed value and it would never move -
   the same trap as Bootstrap's .d-none vs el.style.display.
*/
.progress-fill-start { width: 0; }

/* Sizing steps introduced by the type + Razor-class tranches. */
.w-18px           { width: 18px !important; }
.w-24px           { width: 24px !important; }
.h-18px           { height: 18px !important; }
.h-36px           { height: 36px !important; }
.minw-40px        { min-width: 40px !important; }
.minw-64px        { min-width: 64px !important; }

/* --------------------------------------------------------------------------
   Colour. Every value here is a token, never a literal - the point of moving
   these out of markup is to stop hex leaking into views, so re-introducing it
   in the utility would defeat the exercise (CSS_ARCHITECTURE.md rule 1).

   Bootstrap already ships .text-primary / .bg-primary / .text-white; those are
   reused rather than redefined. .text-online is named for what it marks because
   its value (--color-success, #22c55e) is NOT Bootstrap's success green.
   -------------------------------------------------------------------------- */
.text-pe-ink   { color: var(--pe-ink) !important; }
.text-pe-stone { color: var(--pe-stone) !important; }
.text-pe-mute  { color: var(--pe-mute) !important; }
.bg-pe-cloud   { background: var(--pe-cloud) !important; }

.text-purple   { color: var(--bs-purple) !important; }
.bg-purple     { background-color: var(--bs-purple) !important; }

.text-online   { color: var(--color-success) !important; }
.text-white-80 { color: rgba(255, 255, 255, 0.8) !important; }

.bg-tertiary   { background: var(--bs-tertiary-bg) !important; }
.bg-officer-member    { background: var(--pt-officer-member) !important; }
.bg-officer-appointed { background: var(--pt-officer-appointed) !important; }

/* --------------------------------------------------------------------------
   Layout tail (Phase 2 tranche 4). Everything Bootstrap already covers exactly
   - .rounded-circle, .position-*, .top-50, .translate-middle-y, .font-monospace,
   .opacity-25, .mx-auto, .user-select-none, .border-0, .z-1, .overflow-*,
   .text-break - is reused rather than redefined. These are the values it does
   not cover.
   -------------------------------------------------------------------------- */
.border-1-token         { border: 1px solid var(--bs-border-color) !important; }
.border-3-token         { border: 3px solid var(--bs-border-color) !important; }
.border-start-2-token   { border-left: 2px solid var(--bs-border-color) !important; }
.cursor-grab            { cursor: grab !important; }
.d-contents             { display: contents !important; }
.flex-fill-0            { flex: 1 1 0 !important; }
.flex-fixed-100         { flex: 0 0 100px !important; }
.flex-fixed-90          { flex: 0 0 90px !important; }
.font-mono-code         { font-family: 'Consolas', 'Courier New', monospace !important; }
.maxw-48ch              { max-width: 48ch !important; }
.maxw-500px             { max-width: 500px !important; }
.maxw-56ch              { max-width: 56ch !important; }
.maxw-600px             { max-width: 600px !important; }
.opacity-20             { opacity: .2 !important; }
.start-offscreen        { left: -9999px !important; }
.top-1rem               { top: 1rem !important; }
.top-20px               { top: 20px !important; }
.top-80px               { top: 80px !important; }
.top-auto               { top: auto !important; }
.top-below              { top: calc(100% + 4px) !important; }
.transition-opacity     { transition: opacity .2s !important; }
.w-min450               { width: min(450px, 100vw) !important; }
.z-backdrop             { z-index: var(--z-backdrop) !important; }
.z-max                  { z-index: var(--z-max) !important; }
.z-modal                { z-index: var(--z-modal) !important; }
.z-tooltip              { z-index: var(--z-tooltip) !important; }

/* Sizing steps the tranche-4 tail introduced. */
.h-120px                { height: 120px !important; }
.h-1px                  { height: 1px !important; }
.h-72px                 { height: 72px !important; }
.h-800px                { height: 800px !important; }
.maxh-380px             { max-height: 380px !important; }
.maxh-420px             { max-height: 420px !important; }
.minw-320px             { min-width: 320px !important; }
.w-1px                  { width: 1px !important; }
.w-72px                 { width: 72px !important; }
.h-3px                  { height: 3px !important; }

/* --------------------------------------------------------------------------
   Initial hidden state (see wwwroot/js/shared/init-style.js).

   The markup carries data-init-hidden instead of style="display:none", because
   the CSP style-src directive cannot allow style attributes. This rule hides the
   element until init-style.js converts it back to a real inline style, so there
   is no flash of content.

   The :not([data-init-done]) part is what makes it safe: once the script has run,
   this rule stops matching entirely and the inline style is the only thing in
   play. That matters because the app shows these elements with
   el.style.display = '', which removes the inline declaration - if a class or
   attribute rule were still hiding the element underneath, it could never be
   shown again.
   -------------------------------------------------------------------------- */
[data-init-hidden]:not([data-init-done]) { display: none; }

/* Transmittal timeline dots (tranche 6). The geometry is fixed; only the fill
   colour is server-computed, and that rides on data-css. */
.w-12px                 { width: 12px !important; }
.h-12px                 { height: 12px !important; }
.start-neg23px          { left: -23px !important; }
.top-4px                { top: 4px !important; }

/* --------------------------------------------------------------------------
   Activity badge palette (red flag #63, UI § 12.1).

   The citizen activity grid used to choose a Bootstrap colour class in
   JavaScript from the activity type, which put the palette in two places: the
   partial that renders a badge server-side, and a switch statement in a script.
   Two copies of a palette diverge, and the one in the script is the copy nobody
   thinks to update.

   The script now emits data-activity and nothing else; the colour lives here.
   Adding an activity type means adding a rule here, not editing a switch.
   -------------------------------------------------------------------------- */
/* No colour declaration: Bootstrap's .badge already sets --bs-badge-color, and a hardcoded
   hex here would violate the no-literal-colour rule in Docs/CSS_ARCHITECTURE.md. */
.badge[data-activity]                 { background-color: var(--bs-secondary); }
.badge[data-activity="vote"]          { background-color: var(--bs-primary); }
.badge[data-activity="comment"]       { background-color: var(--bs-info); }
.badge[data-activity="subscription"]  { background-color: var(--bs-success); }

/* --------------------------------------------------------------------------
   Script-rendered badge palette (red flag #63, UI § 12.1 items 7/7a).

   Grid renderers and other scripts emit one attribute and no colour class:
     data-status  a state   (active, pending, failed, meeting-scheduled …)
     data-chip    a label   (count, ordinance, urgent, reference …)
   The colour for every key lives here, so a script can never carry its own
   copy of the palette. State tones follow _StatusBadge (StatusDisplayMapping-
   Extensions): success · secondary · warning (dark text) · danger · info.
   A new key means a new selector here; an unknown key renders neutral.
   -------------------------------------------------------------------------- */
.badge[data-status],
.badge[data-chip]                                    { background-color: var(--bs-secondary); }

/* Solid tones */
.badge:is([data-status="active"], [data-status="approved"], [data-status="success"],
          [data-status="public"], [data-status="visible"], [data-status="published"],
          [data-status="completed"], [data-status="meeting-completed"],
          [data-chip="event"])                       { background-color: var(--bs-success); }
.badge:is([data-status="pending"], [data-status="uploading"], [data-status^="conflict-"],
          [data-status="meeting-postponed"],
          [data-chip="pending"], [data-chip="priority"], [data-chip="pinned"],
          [data-chip="legacy"])                      { background-color: var(--bs-warning); color: var(--bs-dark); }
.badge:is([data-status="failed"], [data-status="suspended"], [data-status="conflict-direct"],
          [data-status="meeting-cancelled"],
          [data-chip="urgent"], [data-chip="overdue"]) { background-color: var(--bs-danger); }
.badge:is([data-status="in-progress"], [data-status="processing"], [data-status="meeting-scheduled"],
          [data-chip="resolution"], [data-chip="verified"], [data-chip="pdf"], [data-chip="speaker"],
          [data-chip="announcement"])                { background-color: var(--bs-info); }
.badge:is([data-status="meeting-in-progress"],
          [data-chip="ordinance"], [data-chip="term"], [data-chip="meeting-type"],
          [data-chip="today"], [data-chip="news"], [data-chip="selected"])
                                                     { background-color: var(--bs-primary); }

/* Soft tones: a tinted chip with matching text */
.badge[data-chip="muted"]                            { background-color: var(--bs-light); color: var(--bs-dark);
                                                       border: var(--bs-border-width) solid var(--bs-border-color); }
.badge:is([data-chip="reference"], [data-chip="item-status"])
                                                     { background-color: var(--bs-primary-bg-subtle); color: var(--bs-primary-text-emphasis);
                                                       border: var(--bs-border-width) solid var(--bs-primary-border-subtle); }
.badge[data-chip="label"]                            { background-color: var(--bs-secondary-bg-subtle); color: var(--bs-secondary-text-emphasis); }
.badge:is([data-chip="adoption"], [data-chip="quorum-met"])
                                                     { background-color: var(--bs-success-bg-subtle); color: var(--bs-success-text-emphasis); }
.badge[data-chip="quorum-short"]                     { background-color: var(--bs-warning-bg-subtle); color: var(--bs-warning-text-emphasis); }
.badge:is([data-chip="custom"], [data-chip="rescheduled"])
                                                     { background-color: var(--bs-warning-bg-subtle); color: var(--bs-warning-text-emphasis);
                                                       border: var(--bs-border-width) solid var(--bs-warning-border-subtle); }

/* Calendar entries carry the meeting's status tone as their left rule. */
.calendar-entry                                      { border-left: 3px solid var(--bs-secondary); }
.calendar-entry[data-status="meeting-scheduled"]     { border-left-color: var(--bs-info); }
.calendar-entry[data-status="meeting-in-progress"]   { border-left-color: var(--bs-primary); }
.calendar-entry[data-status="meeting-completed"]     { border-left-color: var(--bs-success); }
.calendar-entry[data-status="meeting-cancelled"]     { border-left-color: var(--bs-danger); }
.calendar-entry[data-status="meeting-postponed"]     { border-left-color: var(--bs-warning); }
