/* ── Components: the shared fixtures ────────────────────────────────────
   One definition of each: a screen builds from these rather than its own
   classes (AGENTS.md, "Reuse the house fixtures"), and when one falls short
   it is extended here, not forked in a template. Colors come from tokens.css
   only, so every component is right in dark mode without a patch.
   Loaded after base.css (templates/core/_stylesheets.html). */

/* ── Buttons ─────────────────────────────────────────────────────────────
   A bare .btn is the everyday button: surface, hairline, text color. The
   variants change its paint, never its size or shape, so a row of mixed
   buttons lines up. Two sizes besides the default. A text-only action is
   .btn--quiet, an icon or a close button is .btn--icon. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
  padding:8px 13px;font:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.1px;line-height:1.2;
  color:var(--text);background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-sm);
  text-decoration:none;cursor:pointer;white-space:nowrap;box-shadow:none;
  transition:background .15s, border-color .15s, color .15s, transform .08s;
}
.btn:hover:not(:disabled){background:var(--card-2);color:var(--text);text-decoration:none;}
.btn:active{transform:scale(.985);}
.btn:disabled,.btn[aria-disabled="true"]{opacity:.45;cursor:not-allowed;}
.btn--secondary{background:var(--card-2);color:var(--text);border-color:var(--border-strong);}
.btn--primary{background:var(--accent);color:var(--on-accent);border-color:var(--accent);}
.btn--primary:hover:not(:disabled){background:var(--accent-d);border-color:var(--accent-d);color:var(--on-accent);}
.btn--success{background:var(--green);color:var(--on-accent);border-color:var(--green-d);}
.btn--success:hover:not(:disabled){background:var(--green-d);color:var(--on-accent);}
.btn--danger{background:var(--red);color:var(--on-accent);border-color:var(--red-d);}
.btn--danger:hover:not(:disabled){background:var(--red-d);color:var(--on-accent);}
.btn--danger-ghost{background:var(--surface);color:var(--red-d);border-color:var(--red-line);}
.btn--danger-ghost:hover:not(:disabled){background:var(--red-weak);color:var(--red-d);}
.btn--success-ghost{background:var(--surface);color:var(--green-d);border-color:var(--green-line);}
.btn--success-ghost:hover:not(:disabled){background:var(--green-weak);color:var(--green-d);}
.btn--warning{background:var(--amber-weak);color:var(--amber-d);border-color:var(--amber-line);}
.btn--warning:hover:not(:disabled){background:var(--amber-weak);color:var(--amber-d);border-color:var(--amber-d);}
.btn--quiet{background:none;border-color:transparent;color:var(--soft);padding-inline:8px;}
.btn--quiet:hover:not(:disabled){background:var(--card-2);color:var(--text);}
.btn--icon{padding:0;width:30px;height:30px;background:none;border-color:transparent;color:var(--soft);font-size:15px;}
.btn--icon:hover:not(:disabled){background:var(--card-2);color:var(--text);}
.btn--sm{padding:4px 9px;font-size:12px;}
.btn--sm.btn--icon{width:24px;height:24px;padding:0;}
.btn--lg{padding:11px 18px;font-size:14px;}
/* Loading: set by base.html's [data-loading-text] handler on submit. */
@keyframes btn-spin{to{transform:rotate(360deg);}}
.btn.loading{cursor:not-allowed;}
.btn.loading::after{content:'';display:inline-block;width:11px;height:11px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;animation:btn-spin .55s linear infinite;margin-left:8px;vertical-align:middle;}
/* Shown next to a submit button while a blocking OCR/vision/LLM call or a
   background job is in flight (base.html's [data-loading-text] handler). */
.form-processing-note{display:flex;align-items:flex-start;gap:6px;margin-top:8px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:12.5px;line-height:1.4;color:var(--muted);max-width:520px;}
.form-processing-note::before{content:'ℹ️';flex:0 0 auto;}

/* ── Badges and pills ────────────────────────────────────────────────────
   A badge states something about a row (a status, a kind); a pill is a
   filter chip you click. Both come in the same tones, from the tone tokens.
   Which tone a status gets is decided once, in apps.core.badges, and
   rendered with {% badge %}, not re-mapped in each template. */
.badge{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:7px;font-size:10.5px;font-weight:600;letter-spacing:.2px;line-height:1.3;white-space:nowrap;border:1px solid var(--border);background:var(--card);color:var(--soft);}
.badge--green{background:var(--green-weak);color:var(--green-d);border-color:var(--green-line);}
.badge--amber,.badge--yellow{background:var(--amber-weak);color:var(--amber-d);border-color:var(--amber-line);}
.badge--red{background:var(--red-weak);color:var(--red-d);border-color:var(--red-line);}
.badge--blue{background:var(--blue-weak);color:var(--blue-d);border-color:var(--blue-line);}
.badge--purple{background:var(--purple-weak);color:var(--purple-d);border-color:var(--purple-line);}
.badge--orange{background:var(--orange-weak);color:var(--orange-d);border-color:var(--orange-line);}
.badge--gray{background:var(--gray-weak);color:var(--soft);border-color:var(--gray-line);}

.pill{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:7px;font-size:10.5px;font-weight:600;letter-spacing:.2px;line-height:1.3;border:1px solid var(--border);background:var(--card);color:var(--soft);text-decoration:none;}
a.pill:hover,button.pill:hover{border-color:var(--border-strong);color:var(--text);}
button.pill{font:inherit;font-size:10.5px;font-weight:600;cursor:pointer;}
.pill.green{background:var(--green-weak);color:var(--green-d);border-color:var(--green-line);}
.pill.orange{background:var(--orange-weak);color:var(--orange-d);border-color:var(--orange-line);}
.pill.purple{background:var(--purple-weak);color:var(--purple-d);border-color:var(--purple-line);}
.pill.red{background:var(--red-weak);color:var(--red-d);border-color:var(--red-line);}
.pill.amber{background:var(--amber-weak);color:var(--amber-d);border-color:var(--amber-line);}
.pill.blue{background:var(--blue-weak);color:var(--blue-d);border-color:var(--blue-line);}
.pill.muted{background:var(--card);color:var(--muted);border-color:var(--border);}
/* The selected chip in a filter row. Several pages rendered `pill active`
   with nothing defining it, so the chosen filter looked like the rest.
   The hover rule above is more specific than these, so without the :hover
   copies a selected chip took the hover's text color, which is its own
   background, and its label vanished under the mouse. */
.pill.active,.pill.is-active,.pill[aria-current="true"],.pill[aria-current="page"],.pill[aria-pressed="true"],
.pill.active:hover,.pill.is-active:hover,.pill[aria-current="true"]:hover,.pill[aria-current="page"]:hover,.pill[aria-pressed="true"]:hover{
  background:var(--text);color:var(--bg);border-color:var(--text);}

/* ── Cards ───────────────────────────────────────────────────────────────
   The surface every section sits on. Modifiers change padding and paint;
   a screen's own card class adds layout only, never its own border or
   background. */
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:14px;box-shadow:none;}
.card h3{font-size:15px;font-weight:600;margin-bottom:12px;color:var(--text);letter-spacing:-.2px;}
.card h4{font-size:11px;font-weight:600;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px;}
.card--flush{padding:0;}
.card--flush > .table{border:0;}
.card--compact{padding:12px 14px;}
.card--tinted{background:var(--card);}
.card--interactive{cursor:pointer;transition:border-color .15s;}
.card--interactive:hover{border-color:var(--border-strong);}
.card--narrow{max-width:400px;margin:40px auto;}
/* A card that folds: <details class="card card--fold"> whose <summary> is the
   heading (an optional .card__count sits at its right) and whose body is a
   .card__body. Replaces the per-screen fold sections. */
.card--fold{padding:0;}
.card--fold > summary{display:flex;align-items:center;gap:8px;padding:14px 18px;list-style:none;cursor:pointer;font-size:14px;font-weight:600;color:var(--text);}
.card--fold > summary::-webkit-details-marker{display:none;}
.card--fold > summary::before{content:"";flex-shrink:0;border:4px solid transparent;border-left:5px solid var(--muted);border-right:0;transition:transform .15s;}
.card--fold[open] > summary::before{transform:rotate(90deg);}
.card--fold[open] > summary{border-bottom:1px solid var(--border);}
.card--fold > summary > :is(h2,h3,h4){margin:0;font-size:inherit;}
.card__count{margin-left:auto;font-size:12px;font-weight:600;color:var(--muted);}
.card__body{padding:16px 18px;}

/* ── Stat tiles ──────────────────────────────────────────────────────────
   A strip of counts across the top of a screen. A tile that filters the
   list below is a link, and the chosen one is .is-active. */
.stat-strip{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin-bottom:16px;}
.stat-tile{display:block;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px;color:var(--text);text-decoration:none;}
a.stat-tile:hover{border-color:var(--border-strong);color:var(--text);}
.stat-tile.is-active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent);}
.stat-tile__num{display:block;font-size:22px;font-weight:700;line-height:1.1;letter-spacing:-.3px;font-variant-numeric:tabular-nums;}
.stat-tile__label{display:block;margin-top:4px;font-size:11.5px;font-weight:600;color:var(--muted);}
.stat-tile--green .stat-tile__num{color:var(--green-d);}
.stat-tile--amber .stat-tile__num{color:var(--amber-d);}
.stat-tile--red .stat-tile__num{color:var(--red-d);}
.stat-tile--blue .stat-tile__num{color:var(--blue-d);}
.stat-tile--orange .stat-tile__num{color:var(--orange-d);}
.stat-tile--purple .stat-tile__num{color:var(--purple-d);}
/* Dense: a strip inside a card or beside other content, smaller tiles. */
.stat-strip--dense{grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:8px;margin-bottom:12px;}
.stat-strip--dense .stat-tile{padding:8px 10px;border-radius:var(--radius-sm);}
.stat-strip--dense .stat-tile__num{font-size:17px;}
.stat-strip--dense .stat-tile__label{margin-top:2px;font-size:11px;}

/* ── Page head ───────────────────────────────────────────────────────────
   The title row of a screen: its <h1> (emoji allowed here, AGENTS.md), an
   optional line under it, and its actions on the right. */
/* A single column (a form, a wizard) centered on the whole window rather
   than the space beside the sidebar; --window-center-w sets its width.
   Where the window is too narrow to center it there, it starts at the
   content's left edge instead of sliding under the sidebar. */
.window-center{width:min(100%,var(--window-center-w,640px));margin-left:clamp(0px,calc(50vw - var(--window-center-w,640px) / 2 - var(--content-x)),calc(100% - var(--window-center-w,640px)));margin-right:0;}
.page-head{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-bottom:16px;}
.page-head h1{margin:0;font-size:20px;font-weight:600;letter-spacing:-.3px;}
.page-head__sub{margin:4px 0 0;font-size:13px;color:var(--muted);}
.page-head .actions,.page-head__actions{display:flex;gap:8px;flex-wrap:wrap;align-items:center;}

/* ── Filter bar ──────────────────────────────────────────────────────────
   Search, chips and selects in a row above a list, or any short row of
   controls with a button. The global input rule makes every input full
   width, which in a row puts each control on its own line, so controls here
   take their natural width and only a text box stretches. */
.toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:14px;}
.toolbar :is(input,select):not([type=checkbox],[type=radio]){width:auto;padding:8px 10px;font-size:13px;border-radius:var(--radius-sm);}
.toolbar input:is([type=search],[type=text],:not([type])){flex:1 1 12rem;max-width:22rem;}
/* A bare select in a bar gets the field look, or dark mode draws the browser's
   gray default box. */
.toolbar select:not(.select){background:var(--surface);border:1px solid var(--border-strong);color:var(--text);font-family:inherit;}
.toolbar__chips{display:flex;gap:6px;flex-wrap:wrap;}
.toolbar__end{margin-left:auto;}
.toolbar__count{margin-left:auto;font-size:12.5px;color:var(--muted);white-space:nowrap;}

/* ── Fields ──────────────────────────────────────────────────────────────
   A label, its control, and an optional hint or error, from
   templates/core/_field.html ({% field form.x %}). */
.field{display:block;margin-bottom:14px;}
.field>label,.field__label{display:block;font-size:13px;color:var(--muted);margin-bottom:4px;}
/* A choice group (`use_fieldset`) is a fieldset for its legend, without the
   browser's box around it. */
.field>fieldset{border:0;margin:0;padding:0;min-width:0;}
.field select{max-width:100%;}
.field input,.field select,.field textarea{padding:8px 10px;border:1px solid var(--border-strong);border-radius:9px;font-size:14px;background:var(--surface);color:var(--text);}
/* The house select's arrow, a chevron of two token-colored strokes: the
   browser's own sits against the border and ignores padding. Specific
   enough to outlast the `background` shorthands above and in base.css. */
:is(select.select,.field select,.toolbar select):not([multiple]):not([size]){appearance:none;-webkit-appearance:none;padding-right:34px;background-image:linear-gradient(45deg,transparent calc(50% - .8px),var(--muted) calc(50% - .8px) calc(50% + .8px),transparent calc(50% + .8px)),linear-gradient(135deg,transparent calc(50% - .8px),var(--muted) calc(50% - .8px) calc(50% + .8px),transparent calc(50% + .8px));background-position:right 20px center,right 14px center;background-size:6px 6px;background-repeat:no-repeat;}
.field__hint{display:block;margin-top:4px;font-size:12px;line-height:1.4;color:var(--muted);}
.field__error{display:block;margin-top:4px;font-size:12px;line-height:1.4;color:var(--red-d);}
.field--inline{display:flex;align-items:center;gap:8px;}
.field--inline>label{margin:0;}
.field.has-error input,.field.has-error select,.field.has-error textarea{border-color:var(--red-line);}

/* ── Empty states ────────────────────────────────────────────────────────
   templates/core/_empty.html for a section with nothing in it, and
   core/_empty_row.html for a table with no rows. */
.empty{text-align:center;color:var(--muted);background:var(--surface);border:1px dashed var(--border-strong);border-radius:var(--radius);padding:40px 16px;}
.empty__title{display:block;font-size:14px;font-weight:600;color:var(--text);}
.empty__body{margin:6px auto 0;max-width:46ch;font-size:13px;line-height:1.5;}
.empty__action{margin-top:14px;}
.empty-row td,.table .empty-row td{text-align:center;color:var(--muted);padding:28px 12px;}
/* Inside one group of a sheet it is that group's quiet sub-state, not the
   whole table's: an ordinary muted row, so a list of empty groups stays dense. */
tbody[data-sheet-group] .empty-row td{padding:10px 12px;text-align:left;}

/* ── Key-value list ─────────────────────────────────────────────────────*/
.kv{display:grid;grid-template-columns:180px 1fr;gap:6px 16px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;}
.kv dt{color:var(--muted);font-size:13px;}
.kv dd{margin:0;overflow-wrap:anywhere;}
/* A key-value list inside a card that already draws the frame. */
.kv--plain{background:none;border:0;border-radius:0;padding:0;}

/* ── Tables ──────────────────────────────────────────────────────────────
   Every .table keeps its column heads in view: the head sticks under the
   app header as the page scrolls. The rounded corners are clipped with
   overflow:clip, which, unlike overflow:hidden, does not make the table a
   scroll box, so the head sticks to the window. A wrapper that scrolls
   (overflow:auto, .table-scroll) or clips with hidden still traps it; data
   lists belong in the sheet (templates/core/sheet/). The line under the
   head is an inset shadow, because a collapsed border stays behind when the
   cell sticks. */
.table{width:100%;border-collapse:collapse;background:var(--surface);overflow:clip;font-size:13.5px;border:1px solid var(--border);border-radius:var(--radius);}
/* A table whose rows hold a `.menu` lets it open past the table's edge; the
   clip above would cut a last-row menu off. */
.table--menus{overflow:visible;}
.table th,.table td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--border);vertical-align:top;}
.table th{text-transform:uppercase;color:var(--muted);font-size:10.5px;font-weight:600;letter-spacing:.06em;background:var(--surface);}
.table > thead th{position:sticky;top:var(--header-h);z-index:2;border-bottom:0;box-shadow:inset 0 -1px var(--border);}
/* In a horizontal scroller the head can only stick inside it: stay put. */
:is(.table-scroll,.table-wrap,dialog,[style*="overflow-x:auto"],[style*="overflow-x: auto"],[style*="overflow:auto"],[style*="overflow: auto"]) .table > thead th{top:0;}
.table tbody tr{transition:background .12s;}
.table tbody tr:hover{background:var(--card-2);}
/* A row that needs attention (rent charged and not paid), tinted red. */
.table tbody tr.table__row--alert{background:var(--red-weak);}
.table tr:last-child td{border-bottom:0;}
.table th a{color:inherit;}
.table .num,.table td.num,.table th.num{text-align:right;font-variant-numeric:tabular-nums;}
.table-scroll{overflow-x:auto;}
.row-actions{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end;}
/* On a <td> itself, flex breaks the row's height and misaligns its border;
   there it stays a table cell and lays its actions out inline. */
td.row-actions{display:table-cell;text-align:right;}
td.row-actions>a,td.row-actions>button,td.row-actions>form,td.row-actions>.btn{display:inline-block;vertical-align:top;margin-left:6px;}
/* The "View proof" disclosure row in _wo_table_rows.html's report variant:
   an iframe of the whole WO page under the row it belongs to. Not a data
   row, so no hover tint. */
.wo-proof-row td{background:var(--card-2);padding:10px 4px;}
.wo-proof-row:hover td{background:var(--card-2);}
.wo-proof-frame{width:100%;height:70vh;border:0;border-radius:var(--radius-sm);background:var(--surface);}

/* ── Pager ───────────────────────────────────────────────────────────────
   templates/core/_pager.html. */
.pager{display:flex;gap:12px;align-items:center;margin-top:14px;color:var(--muted);font-size:13px;}
.pager__where{min-width:0;}
.pager--end{justify-content:flex-end;gap:8px;}
.pager--end .btn{padding:1px 7px;font-size:13px;line-height:1.3;}
/* Previous / this / next week (templates/core/_week_stepper.html). */
.stepper{display:inline-flex;align-items:center;gap:8px;white-space:nowrap;}

/* ── Typeahead (static/js/typeahead.js) ──────────────────────────────────
   A search box whose matches open in a list under it to pick from. */
.typeahead{position:relative;}
.typeahead__menu{position:absolute;z-index:30;top:calc(100% + 4px);left:0;right:0;min-width:16rem;max-height:260px;overflow-y:auto;background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-sm);box-shadow:var(--shadow-lg);}
.typeahead__opt{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 8px;width:100%;padding:7px 10px;border:0;border-bottom:1px solid var(--border);background:none;font:inherit;font-size:13px;color:inherit;text-align:left;cursor:pointer;}
.typeahead__opt:last-child{border-bottom:0;}
.typeahead__opt:hover,.typeahead__opt.is-active{background:var(--accent-weak);}
.typeahead__sub{color:var(--muted);}
.typeahead__note{flex-basis:100%;font-size:12px;color:var(--muted);}
.typeahead__none{padding:8px 10px;font-size:12.5px;color:var(--muted);}

/* ── Tabs ────────────────────────────────────────────────────────────────
   .page-tabs: a page's own tab strip (apps.core.page_tabs, swapped in place
   by static/js/tab_swap.js). .inline-tabs: a toggle between views inside one
   section (templates/core/_inline_tabs.html, static/js/ui.js). */
.page-tabs{display:flex;gap:.25rem;flex-wrap:wrap;background:var(--surface);border-bottom:1px solid var(--border-strong);margin:.75rem 0 1rem;}
.page-tab{background:none;border:none;border-bottom:2px solid transparent;margin-bottom:-1px;padding:.5rem .85rem;
  border-radius:0;font-family:inherit;font-size:.92rem;font-weight:600;color:var(--muted);cursor:pointer;text-decoration:none;}
.page-tab:hover{color:var(--text);}
.page-tab.is-active{color:var(--text);border-bottom-color:var(--accent);}
/* Tabs keep stretching to the band's bottom rule; only the actions center. */
.page-tabs__actions{margin-left:auto;align-self:center;display:flex;gap:.5rem;align-items:center;padding:.25rem 0;}
.inline-tabs{display:inline-flex;border:1px solid var(--border-strong);border-radius:9px;overflow:clip;background:var(--surface);}
.inline-tabs__tab{border:0;background:none;font:inherit;font-size:13px;font-weight:600;color:var(--soft);padding:6px 14px;cursor:pointer;}
.inline-tabs__tab + .inline-tabs__tab{border-left:1px solid var(--border-strong);}
.inline-tabs__tab:hover{color:var(--text);background:var(--card-2);}
.inline-tabs__tab[aria-selected="true"],.inline-tabs__tab[aria-current="page"]{background:var(--accent);color:var(--on-accent);}
/* The same control as links, for two pages that are views of one thing and
   carry a parameter across (Day / Week keep ?date=): <nav class="inline-tabs">
   of <a class="inline-tabs__tab">, the current one aria-current="page". No
   role=tablist, so static/js/ui.js leaves it alone and each is a real link. */
a.inline-tabs__tab{display:inline-flex;align-items:center;text-decoration:none;}
a.inline-tabs__tab:hover{text-decoration:none;}

/* ── Drawer ──────────────────────────────────────────────────────────────
   A side panel over the right of the page, filled by htmx
   (templates/core/_drawer.html). Empty means closed; its close button and
   Escape empty it (static/js/ui.js). */
.drawer{position:fixed;top:var(--header-h);right:0;bottom:0;z-index:30;width:min(440px,100vw);overflow:auto;padding:18px 20px;background:var(--surface);border-left:1px solid var(--border);box-shadow:var(--shadow-lg);}
.drawer:empty{display:none;}
.drawer__head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin:0 0 12px;}
.drawer__title{margin:0;font-size:1.1rem;}
.drawer__sec{margin:0 0 18px;}
.drawer__sec > h3{margin:0 0 6px;font-size:10.5px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);}
.drawer__list{list-style:none;margin:0;padding:0;font-size:13px;}
.drawer__list > li{display:flex;justify-content:space-between;gap:10px;padding:6px 0;border-bottom:1px solid var(--border);}
.drawer__list > li:last-child{border-bottom:0;}
.drawer__kv{display:grid;grid-template-columns:max-content 1fr;gap:4px 14px;margin:0;font-size:13px;}
.drawer__kv dt{color:var(--muted);}
.drawer__kv dd{margin:0;}
.drawer__note{margin:0 0 12px;font-size:12.5px;line-height:1.5;color:var(--muted);}
.drawer__danger{margin:14px 0 0;padding-top:12px;border-top:1px solid var(--border);}
.drawer .field input,.drawer .field select{width:100%;}

/* ── Dialog ──────────────────────────────────────────────────────────────
   A native <dialog> (templates/core/_dialog.html) for content already on
   the page: [data-dialog-open="#id"] opens it, [data-dialog-close], the
   backdrop and Escape close it (static/js/ui.js). An htmx fragment opens in
   #oasis-modal instead (below). */
dialog.dialog{margin:auto;width:min(640px,94vw);max-height:90vh;padding:20px;border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--surface);color:var(--text);box-shadow:var(--shadow-lg);}
dialog.dialog::backdrop{background:var(--overlay);}
/* dialog.dialog above sets the width, so the wide size must match its
   specificity or it never applies. */
dialog.dialog--lg{width:min(1100px,96vw);}
/* A dialog whose content switches in place (inline tabs, as in the workflow
   builder's step dialog) sits at a fixed top instead of centering, so a
   shorter panel shrinks it from the bottom and its tabs stay where they
   were clicked. */
dialog.dialog:has(.inline-tabs){margin-block:5vh auto;}
.dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 12px;}
.dialog__title{margin:0;font-size:16px;font-weight:700;}
.dialog__foot{display:flex;justify-content:flex-end;gap:8px;margin-top:16px;}

/* ── How-to note ─────────────────────────────────────────────────────────
   "What is this screen?" guide, on <details>: open until learned, then
   folded (the state is remembered per id; base.html). */
.howto{margin-bottom:16px;overflow:hidden;border:1px solid var(--border);border-left:3px solid var(--border-strong);background:var(--card);border-radius:10px;}
.howto>summary{cursor:pointer;list-style:none;padding:11px 15px;font-size:13px;font-weight:600;color:var(--soft);display:flex;align-items:center;gap:8px;user-select:none;}
.howto>summary::-webkit-details-marker{display:none;}
.howto>summary:hover{background:var(--card-2);}
.howto>summary .howto-chev{margin-left:auto;color:var(--muted);font-size:11px;font-weight:400;transition:transform .15s;}
.howto[open]>summary .howto-chev{transform:rotate(180deg);}
.howto-body{padding:3px 16px 15px;font-size:12.5px;line-height:1.65;color:var(--text);}
.howto-body p{margin:0 0 9px;}
.howto-body p:last-child{margin-bottom:0;}
.howto-body b{color:var(--text);}
.howto-meta{color:var(--muted);font-style:italic;}
/* To-do tab dropdowns: plain <details>, so hide the triangle here too. */
#task-stack-card>summary::-webkit-details-marker{display:none;}
#task-stack-card>summary:hover{opacity:.85;}

/* ── Help tip (core/_help_tip.html, {% help_tip %}) ──────────────────────
   A circled question mark whose card opens on hover or keyboard focus and
   closes on Escape (static/js/ui.js). Desktop only: a phone has no hover, so
   the field app explains things inline. The top bar's "What is this page?"
   mark is the same fixture (templates/base.html), sized up by --page. The
   card is placed absolutely, so opening it never moves anything. */
.help-tip{position:relative;display:inline-flex;vertical-align:middle;flex-shrink:0;font-size:12.5px;font-weight:400;line-height:1.6;text-transform:none;letter-spacing:normal;}
.help-tip__mark{width:18px;height:18px;padding:0;margin:0;border-radius:50%;border:1.5px solid var(--border-strong);background:transparent;color:var(--soft);font:inherit;font-size:11px;font-weight:700;line-height:1;display:grid;place-items:center;cursor:help;transition:border-color .12s,color .12s;}
.help-tip__mark:focus{outline:none;}
.help-tip__mark:focus-visible{box-shadow:0 0 0 3px var(--orange-weak);}
.help-tip:hover .help-tip__mark,.help-tip__mark:focus-visible{border-color:var(--orange);color:var(--orange);}
/* The outer box is a transparent bridge, so the pointer can travel from the
   mark into the card without it closing. Near the right edge it opens
   leftward instead (ui.js adds --flip); --end always opens leftward. */
.help-tip__pop{display:none;position:absolute;top:100%;left:-10px;padding-top:6px;z-index:200;}
.help-tip:hover .help-tip__pop,.help-tip:focus-within .help-tip__pop{display:block;}
.help-tip.is-dismissed .help-tip__pop{display:none;}
.help-tip--end .help-tip__pop,.help-tip--flip .help-tip__pop{left:auto;right:-10px;}
.help-tip__card{display:block;width:max-content;max-width:min(320px,60vw);padding:10px 14px;background:var(--card);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-lg);font-size:12.5px;line-height:1.6;color:var(--text);font-weight:400;text-align:left;white-space:normal;}
.help-tip__card > .howto-body{padding:0;}
.help-tip__card p{margin:0 0 9px;}
.help-tip__card ul,.help-tip__card ol{margin:0 0 9px;padding-left:1.3em;}
.help-tip__card > :last-child,.help-tip__card p:last-child{margin-bottom:0;}
.help-tip--page .help-tip__mark{width:22px;height:22px;font-size:13px;}
.help-tip--page .help-tip__pop{left:-12px;padding-top:8px;}
.help-tip--page .help-tip__card{width:min(460px,60vw);max-width:none;max-height:70vh;overflow:auto;padding:12px 16px;}

/* ── Notes ───────────────────────────────────────────────────────────────
   A quiet callout in the page flow. (.alert-* are older names.) */
.alert{padding:11px 14px;border:1px solid var(--border);border-radius:10px;background:var(--card);margin-bottom:14px;color:var(--text);}
.alert-info{border-left:3px solid var(--border-strong);}
.alert-success{border-left:3px solid var(--green);}
.alert-warning{border-left:3px solid var(--yellow);}
.alert-danger{border-left:3px solid var(--red);}
/* The reset in base.css zeroes list padding, which hangs a list's bullets
   outside the note, over its border and the sidebar. */
.alert ul,.alert ol{margin:4px 0 0;padding-left:1.3em;}
.alert li + li{margin-top:2px;}

/* ── Progress bar ────────────────────────────────────────────────────────
   <progress class="progress" value max>: native, so the fill needs no
   style="" width. Tones --green, --amber, --red, --blue; --thin for a bar
   under a line of text. core/_progress.html adds a label and a count. */
.progress{--progress-fill:var(--accent);appearance:none;-webkit-appearance:none;display:block;width:100%;height:8px;border:0;border-radius:99px;background:var(--card-2);overflow:hidden;}
.progress::-webkit-progress-bar{background:var(--card-2);border-radius:99px;}
.progress::-webkit-progress-value{background:var(--progress-fill);border-radius:99px;transition:width .2s;}
.progress::-moz-progress-bar{background:var(--progress-fill);border-radius:99px;}
.progress--thin{height:5px;}
.progress--green{--progress-fill:var(--green);}
.progress--amber{--progress-fill:var(--amber);}
.progress--red{--progress-fill:var(--red);}
.progress--blue{--progress-fill:var(--blue);}
.progress-line{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--muted);}
.progress-line > .progress{flex:1;}
.progress-line__count{font-variant-numeric:tabular-nums;white-space:nowrap;}

/* ── Wizard steps ────────────────────────────────────────────────────────
   The progress bar over a task split across pages: core/_wizard_steps.html.
   A thin .progress filled to the middle of the current page's column, so
   the fill ends under the page you are on, then the pages as numbered
   links. .wizard-nav is the Back / Continue row under each page. */
/* A <nav>, so it opts out of base.css's old tab-bar nav rule. */
.wizard-steps{display:block;margin:0 0 18px;background:none;border:0;}
.wizard-steps__list{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:12px;margin:8px 0 0;padding:0;list-style:none;}
.wizard-steps__step a,.wizard-steps__here{display:flex;align-items:center;justify-content:center;gap:8px;font-size:13px;color:var(--muted);text-decoration:none;}
.wizard-steps__step a:hover{color:var(--text);}
.wizard-steps__num{display:inline-grid;place-items:center;width:22px;height:22px;border-radius:50%;border:1px solid var(--border-strong);font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;flex-shrink:0;}
.wizard-steps__step--done .wizard-steps__num{border-color:var(--accent);color:var(--accent);}
.wizard-steps__step--current a,.wizard-steps__step--current .wizard-steps__here{color:var(--text);font-weight:600;}
/* The label reserves its bold width (a hidden bold copy from data-label in
   a zero-height second row), so bolding the current page never widens it
   and the centered number and label stay put from page to page. */
.wizard-steps__label{display:inline-grid;justify-items:center;}
.wizard-steps__label::after{content:attr(data-label);font-weight:600;height:0;overflow:hidden;visibility:hidden;}
.wizard-steps__step--current .wizard-steps__num{border-color:var(--accent);background:var(--accent);color:var(--on-accent);}
.wizard-nav{display:flex;align-items:center;gap:10px;margin:22px 0 8px;padding-top:16px;border-top:1px solid var(--border);}
.wizard-nav__note{color:var(--muted);font-size:12.5px;}
.wizard-nav__end{display:flex;align-items:center;gap:10px;margin-left:auto;}

/* ── Choice row ──────────────────────────────────────────────────────────
   Radio (or checkbox) options drawn as small cards: a div.choice-row of
   <label>s, each holding its input. A forms.RadioSelect whose widget attrs
   carry class "choice-row" draws the same, since Django puts the class on
   the wrapper <div> (and on each input, which div.choice-row skips). */
div.choice-row{display:flex;flex-wrap:wrap;gap:8px;}
div.choice-row > div{margin:0;}
div.choice-row label{display:flex;align-items:center;gap:6px;padding:8px 13px;border:1px solid var(--border-strong);border-radius:9px;background:var(--card);font-size:13.5px;font-weight:500;color:var(--text);cursor:pointer;}
div.choice-row label:hover{border-color:var(--accent);}
/* The picked option reads bolder through a hairline stroke, not a heavier
   weight: bold glyphs are wider, so the card grew on click. */
div.choice-row label:has(input:checked){border-color:var(--accent);background:var(--accent-weak);-webkit-text-stroke:.4px currentColor;}
/* Keyboard focus rings the whole card in the house focus blue (base.css),
   not the accent, so a focused option never reads as a picked one; the
   input's own ring would draw a second box inside the card. */
div.choice-row label:has(input:focus-visible){outline:2px solid var(--blue);outline-offset:2px;}
div.choice-row label input:focus-visible{outline:none;}
/* --cards: bigger options holding a .choice-row__text (a bold name over a
   muted line), laid out on an even grid; --stack puts one per row. */
div.choice-row--cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;}
div.choice-row--stack{grid-template-columns:minmax(0,1fr);}
div.choice-row--cards label{align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;font-weight:400;}
div.choice-row--cards label:has(input:checked){-webkit-text-stroke:0;border-width:2px;padding:11px 13px;}
div.choice-row--cards label > input{margin:3px 0 0;flex-shrink:0;}
.choice-row__text{display:grid;gap:3px;min-width:0;}
.choice-row__text strong{font-size:14px;font-weight:650;}
.choice-row__text small{font-size:12.5px;color:var(--muted);}

/* ── Dropdown menu ───────────────────────────────────────────────────────
   <details class="menu"> whose <summary> is a .btn and whose .menu__items
   pops under it, right-aligned (--left to open from the left edge, --wide
   for a form inside). Items are links or .btn.btn--quiet.menu__btn rows; a
   .menu__sep divides groups, a .menu__hint explains one. */
.menu{position:relative;display:inline-block;}
.menu > summary{list-style:none;cursor:pointer;}
.menu > summary::-webkit-details-marker{display:none;}
.menu__items{position:absolute;right:0;top:calc(100% + 4px);z-index:60;width:max-content;min-width:180px;max-width:320px;padding:6px;background:var(--surface);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-lg);}
.menu__items--left{right:auto;left:0;}
.menu__items--wide{min-width:300px;max-width:340px;}
.menu__items a{display:block;padding:8px 12px;border-radius:7px;font-size:13px;color:var(--text);text-decoration:none;}
.menu__items a:hover{background:var(--accent-weak);color:var(--accent);}
.menu__items form{margin:0;}
.menu__btn{width:100%;justify-content:flex-start;}
.menu__sep{height:1px;margin:6px 4px;background:var(--border);border:0;}
.menu__hint{margin:2px 4px 8px;font-size:12px;line-height:1.4;color:var(--muted);}

/* ── Layout helpers ──────────────────────────────────────────────────────
   For the spacing that inline style="" attributes used to carry. */
.cluster{display:flex;flex-wrap:wrap;align-items:center;gap:8px;}
.spread{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;}
.num{text-align:right;font-variant-numeric:tabular-nums;}
.nowrap{white-space:nowrap;}
.mt-0{margin-top:0;}
.mb-0{margin-bottom:0;}
.m-0{margin:0;}
/* Spacing: .stack spaces its children 14px apart; --tight 8px, --loose 24px.
   .mt-sm/.mt-md/.mt-lg (6, 12, 24px) for the one gap a stack cannot give. */
.stack--tight>*+*{margin-top:8px;}
.stack--loose>*+*{margin-top:24px;}
.mt-sm{margin-top:6px;}
.mt-md{margin-top:12px;}
.mt-lg{margin-top:24px;}
/* Older layout names some vendor pages were written with. */
.container{width:100%;max-width:1100px;}
.row{display:flex;flex-wrap:wrap;gap:16px;}
.col{flex:1 1 0;min-width:0;}
.w-100{width:100%;}
.small,small{font-size:12px;}
.border-top{border-top:1px solid var(--border);}
.border-bottom{border-bottom:1px solid var(--border);}

/* ── Legacy tiles (ai agents), until that screen moves to .stat-tile ────*/
.tiles{display:flex;gap:14px;flex-wrap:wrap;margin:18px 0;}
.tile{flex:1 1 150px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:18px;text-align:center;text-decoration:none;color:var(--text);display:block;}
a.tile:hover{border-color:var(--accent);}
.tile.is-active{border-color:var(--accent);box-shadow:1px 2px 8px var(--accent-weak);}
#toast{position:fixed;bottom:20px;right:20px;background:var(--green);color:#fff;padding:11px 18px;border-radius:10px;font-size:13px;font-weight:600;opacity:0;transition:opacity .3s;pointer-events:none;z-index:999;}
#toast.show{opacity:1;}

/* ── File inputs ─────────────────────────────────────────────────────────
   The browser's own "Choose File" button, drawn as a .btn. */
input[type="file"]{color:var(--muted);font:inherit;font-size:12.5px;}
input[type="file"]::file-selector-button,
input[type="file"]::-webkit-file-upload-button{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
  margin-right:10px;padding:8px 14px;font:inherit;font-size:12.5px;font-weight:600;
  letter-spacing:-.1px;color:var(--text);background:var(--surface);
  border:1px solid var(--border-strong);border-radius:8px;cursor:pointer;
  transition:background .15s, transform .08s, box-shadow .15s;
}
input[type="file"]::file-selector-button:hover,
input[type="file"]::-webkit-file-upload-button:hover{background:var(--card);}
input[type="file"]:disabled::file-selector-button,
input[type="file"]:disabled::-webkit-file-upload-button{opacity:.45;cursor:not-allowed;}

/* ── Modal for htmx fragments (WO card, Ground Truth) ────────────────── */
.oasis-modal{position:fixed;inset:0;z-index:1000;display:none;}
.oasis-modal.open{display:block;}
.oasis-modal__backdrop{position:absolute;inset:0;background:var(--overlay);backdrop-filter:blur(1px);}
.oasis-modal__panel{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:min(720px,94vw);max-height:90vh;overflow:auto;background:var(--surface);
  border-radius:var(--radius-lg);box-shadow:var(--shadow-lg);padding:18px;}
/* --lg: a full screen swapped in (e.g. the Link-to-work-order review), not a
   small read-only fragment (Ground Truth, WO email view) — selected off a
   marker class on the swapped-in content so the ~20 existing small-fragment
   modal usages are untouched. */
.oasis-modal__panel:has(.oasis-modal-body--lg){width:min(1200px,96vw);max-height:94vh;}
.oasis-modal__x{position:absolute;top:10px;right:12px;width:30px;height:30px;border:none;
  border-radius:var(--radius-sm);background:var(--card-2);color:var(--soft);font-size:15px;cursor:pointer;z-index:2;}
.oasis-modal__x:hover{background:var(--border-strong);color:var(--text);}
/* The stacked "peek" popup (Ground Truth / Email View) — same shell, higher
   z-index so it renders ON TOP of a primary modal (e.g. Email View opened
   from inside the Link-this-bill modal) instead of destroying it. */
.oasis-modal--peek{z-index:1100;}
.oasis-modal--peek .oasis-modal__panel{width:min(640px,92vw);}
/* Instant-open modal spinner (shown the moment a modal fragment is requested). */
.modal-loading{display:flex;align-items:center;justify-content:center;gap:10px;
  min-height:120px;color:var(--muted);font-size:14px;}
.modal-spinner{width:18px;height:18px;border-radius:50%;border:2px solid var(--border-strong);
  border-top-color:var(--orange);animation:modal-spin .7s linear infinite;}
@keyframes modal-spin{to{transform:rotate(360deg);}}
@media (prefers-reduced-motion:reduce){.modal-spinner{animation-duration:1.6s;}}
/* ── Click-to-sort tables (static/js/sheet.js; opt out via data-no-sort) ──
   Any .table with a head and a body sorts on a header click. The look of a
   sortable header comes from the table's shape, not from the th-sort class,
   so a table swapped in over htmx lays out once: sheet.js adds the class
   after the swap, and headers that only then gained an arrow and stopped
   wrapping re-flowed the whole table (2026-09-26). Same conditions as
   sheet.js's `sortable()`. A sheet's own table marks its sortable heads with
   data-sort-key instead and is styled below. */
table.table:not([data-no-sort],[data-sheet-table]):not(:has(> tfoot, [colspan])):has(> tbody > tr) > thead > tr:last-child > th:not([data-no-sort]){white-space:nowrap;}
table.table:not([data-no-sort],[data-sheet-table]):not(:has(> tfoot, [colspan])):has(> tbody > tr) > thead > tr:last-child > th:not([data-no-sort]):not(.th-sort-asc):not(.th-sort-desc)::after{content:'↕';opacity:.3;font-size:10px;margin-left:6px;}
th.th-sort,th[data-sort-key]{cursor:pointer;user-select:none;white-space:nowrap;}
th.th-sort:hover,th[data-sort-key]:hover{color:var(--accent);}
th.th-sort::after,th[data-sort-key]::after{content:'↕';opacity:.3;font-size:10px;margin-left:6px;}
th.th-sort-asc::after,th[data-sort-key][aria-sort="ascending"]::after{content:'↑';opacity:1;color:var(--accent);}
th.th-sort-desc::after,th[data-sort-key][aria-sort="descending"]::after{content:'↓';opacity:1;color:var(--accent);}

/* ── Sheet (templates/core/sheet/, apps.core.sheets) ────────────────────
   The filter bar is a .toolbar. Chips are radios wearing a .pill, so the
   bar is one plain form in both modes and the chosen chip survives a reload.
   The rows region dims while a server-mode re-fetch is out, after a beat so
   a quick answer never flickers; it stays usable throughout. */
.sheet__controls{display:contents;}
.sheet__chip{display:inline-flex;cursor:pointer;}
.sheet__chip input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none;}
.sheet__chip input:checked + .pill{background:var(--text);color:var(--bg);border-color:var(--text);}
.sheet__chip input:focus-visible + .pill{outline:2px solid var(--accent);outline-offset:2px;}
.sheet__chip:hover .pill{border-color:var(--border-strong);color:var(--text);}
.sheet__chip:hover input:checked + .pill{color:var(--bg);}
.sheet__summary,.card-list__summary{min-height:18px;margin:0 0 8px;font-size:12.5px;color:var(--muted);}
.sheet__rows,.card-list__body{transition:opacity .15s .15s;}
.sheet:has(> .sheet__bar.htmx-request) .sheet__rows,.card-list:has(> .card-list__bar.htmx-request) .card-list__body{opacity:.6;}
:is(.sheet__table,.table) tr[data-sheet-group-head] > *{background:var(--card-2);color:var(--text);font-weight:600;text-transform:none;letter-spacing:0;font-size:12.5px;}
.sheet__after{margin-top:20px;}
.sheet__after > summary{cursor:pointer;font-weight:600;font-size:13px;color:var(--muted);}
.sheet__after > summary ~ *{margin-top:10px;}
/* A cell's second line (a unit's street under its number). */
.sheet__sub{display:block;font-size:11.5px;color:var(--muted);}
/* Grouped sheets: a group's head row (a building over its apartments),
   collapsible from its toggle (static/js/sheet.js), with its rows indented. */
.sheet__group{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
.sheet__group-title{padding:0;border:0;background:none;font:inherit;font-weight:700;color:inherit;text-align:left;cursor:pointer;}
.sheet__group-title:hover{color:var(--blue-d);text-decoration:underline;}
.sheet__group-count{margin-left:auto;font-size:12px;font-weight:500;color:var(--muted);white-space:nowrap;}
.sheet__toggle{padding:4px;border:0;background:none;line-height:0;color:var(--muted);cursor:pointer;transition:transform .12s;}
.sheet__chev{display:inline-block;width:7px;height:7px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:translateY(-2px) rotate(45deg);}
tbody[data-collapsed] .sheet__toggle{transform:rotate(-90deg);}
tbody[data-collapsed] > tr:not([data-sheet-group-head]){display:none;}
.sheet__table tbody[data-sheet-group] > tr:not([data-sheet-group-head]) > td:first-child{padding-left:40px;}
.sheet__skel-count{width:7rem;}
/* A lazy sheet's placeholder runs past the fold, so the footer never moves. */
.sheet > .lazy-section > .sheet__rows{min-height:calc(100vh - var(--header-h, 56px));}

/* ── The card list (templates/core/card_list/, apps.core.card_lists): .card
   items with a shared head, count, empty state and pager. A tone is a rail. */
.card-list__head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px;}
.card-list__heading{margin:0;font-size:15px;}
.card-list__actions{margin-left:auto;display:flex;gap:8px;flex-wrap:wrap;}
.card-list__items{display:flex;flex-direction:column;gap:8px;}
.card-list__items--grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(330px,100%),1fr));gap:12px;}
.card-list__items--wide{grid-template-columns:repeat(auto-fill,minmax(min(420px,100%),1fr));gap:16px;}
.card-list__items > .card{margin:0;}
.card-list__item{display:flex;flex-direction:column;gap:6px;min-width:0;padding:12px 14px;color:var(--text);text-decoration:none;}
a.card-list__item:hover{color:var(--text);}
.card-list__item.is-active,.card-list__item[aria-current]{background:var(--accent-weak);border-color:var(--accent);}
.card-list__top{display:flex;align-items:baseline;justify-content:space-between;gap:8px;min-width:0;}
.card-list__lead{min-width:0;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
button.card-list__lead{padding:0;border:0;background:none;font:inherit;font-weight:650;color:var(--accent);text-align:left;cursor:pointer;}
button.card-list__lead:hover{text-decoration:underline;}
.card-list__meta{flex-shrink:0;font-size:12px;color:var(--muted);white-space:nowrap;}
.card-list__text{font-size:13px;line-height:1.4;color:var(--soft);}
.card-list__foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:auto;}
.card-list__foot > .card-list__meta:last-child{margin-left:auto;}
.card-list__item--amber{border-left:4px solid var(--amber);}
.card-list__item--blue{border-left:4px solid var(--blue);}
.card-list__item--green{border-left:4px solid var(--green);}
.card-list__item--red{border-left:4px solid var(--red);}
.card-list__item--gray{border-left:4px solid var(--gray-line);}
.card-list__item--orange{border-left:4px solid var(--orange);}
.card-list__item--pulse{animation:card-list-pulse 1.8s ease-out infinite;}
@keyframes card-list-pulse{0%{box-shadow:0 0 0 0 var(--blue-line);}70%{box-shadow:0 0 0 6px transparent;}100%{box-shadow:0 0 0 0 transparent;}}
/* Dense: borderless rows for a narrow column (a reader's message list). */
.card-list__items--dense{gap:2px;}
.card-list__items--dense > .card-list__item{gap:2px;padding:9px 11px;border-color:transparent;border-left:3px solid transparent;border-radius:10px;background:none;}
.card-list__items--dense > .card-list__item:hover{background:var(--accent-weak);}
.card-list__items--dense > .card-list__item:is(.is-active,[aria-current]){background:var(--accent-weak);border-left-color:var(--accent);}
.card-list__items--dense .card-list__lead{font-size:12.5px;color:var(--soft);}
.card-list__items--dense .card-list__meta{font-size:11px;}
.card-list__skel{min-height:84px;}.card-list__items--dense > .card-list__skel{min-height:52px;}
.card-list__skel-lead{width:40%;}.card-list__skel-meta{width:3rem;}.card-list__skel-text{width:75%;}

/* ── Sort-direction caret, CSS triangle (not a unicode glyph, which renders
   inconsistently across fonts/platforms) — used by the server-rendered
   sortable column headers, e.g. the Records report's Status column. ── */
.sort-caret{display:inline-block;width:0;height:0;margin-left:5px;vertical-align:1px;
  border-left:4px solid transparent;border-right:4px solid transparent;}
.sort-caret--asc{border-bottom:5px solid currentColor;}
.sort-caret--desc{border-top:5px solid currentColor;}

/* ── Lazy-loaded section placeholder ───────────────────────────────────────
   Shown by a section that fetches its content in after the page shell paints
   (hx-get + hx-trigger; see _lazy_skeleton.html, work_orders.html,
   job_schedule.html, people/list.html). Built from theme tokens so it flips
   with the light/dark contract above without a separate dark rule. */
.lazy-skeleton{display:flex;flex-direction:column;gap:12px;max-width:640px;padding:8px 0;}
.lazy-skeleton__label{color:var(--muted);font-size:13px;font-weight:600;}
.lazy-skeleton__bar{
  display:block;height:14px;width:100%;border-radius:7px;
  background:linear-gradient(90deg,var(--card-2) 25%,var(--border-strong) 37%,var(--card-2) 63%);
  background-size:400% 100%;
  animation:lazy-shimmer 1.5s ease-in-out infinite;
}
.lazy-skeleton__bar:nth-child(3){width:92%;animation-delay:.1s;}
.lazy-skeleton__bar:nth-child(4){width:74%;animation-delay:.2s;}
.lazy-skeleton__bar:nth-child(5){width:84%;animation-delay:.3s;}
@keyframes lazy-shimmer{from{background-position:100% 50%;}to{background-position:0 50%;}}
@media (prefers-reduced-motion:reduce){.lazy-skeleton__bar,.card-list__item--pulse{animation:none;}}

/* ── Shaped skeletons ───────────────────────────────────────────────────
   Same shimmer as .lazy-skeleton above, but sized to drop straight into the
   real markup (a .card/.table row, a map canvas, an org-chart node) so the
   unloaded state occupies exactly the space the loaded one will, and the
   htmx swap causes no layout shift. .skeleton-bar for a line of text,
   .skeleton-box for a shaped block (a map, a badge, a button). */
.skeleton-bar,.skeleton-box{
  background:linear-gradient(90deg,var(--card-2) 25%,var(--border-strong) 37%,var(--card-2) 63%);
  background-size:400% 100%;
  animation:lazy-shimmer 1.5s ease-in-out infinite;
}
.skeleton-bar{display:inline-block;height:12px;border-radius:6px;vertical-align:middle;}
/* A real control (a .pill, a .btn, an input) standing in as its own
   placeholder: the same box, text and all, so its size comes from the same CSS
   and fonts as the control it stands for, with only the paint swapped for the
   shimmer. Fixed-pixel bars matched on one machine and drifted on another
   (2026-09-27). */
.skeleton-shape,.skeleton-shape:hover{
  color:transparent !important;border-color:transparent !important;box-shadow:none !important;
  background:linear-gradient(90deg,var(--card-2) 25%,var(--border-strong) 37%,var(--card-2) 63%) !important;
  background-size:400% 100% !important;
  animation:lazy-shimmer 1.5s ease-in-out infinite;pointer-events:none;user-select:none;
}
.skeleton-shape::placeholder{color:transparent;}
/* What is inside a painted control (a tile's number, a button's divider)
   keeps its space but none of its own ink. */
.skeleton-shape *{color:transparent !important;border-color:transparent !important;}
.skeleton-box{display:block;border-radius:8px;}
.skeleton-row td{padding:9px 8px;border-top:1px solid var(--border);}
/* The pieces in templates/core/skeletons/: a table cell, a card of lines, a
   form, a canvas, a week or month grid. */
.skeleton-cell{width:70%;max-width:12rem;}
.skeleton-row:nth-child(3n+2) .skeleton-cell{width:55%;}
.skeleton-row:nth-child(3n) .skeleton-cell{width:80%;}
th > .skeleton-cell{width:55%;height:10px;}
.skeleton-lines{display:flex;flex-direction:column;gap:12px;}
.skeleton-lines > .skeleton-bar{display:block;width:88%;}
.skeleton-lines > .skeleton-bar:first-child{width:35%;height:14px;}
.skeleton-lines > .skeleton-bar:nth-child(3n){width:70%;}
.skeleton-lines > .skeleton-bar:nth-child(3n+4){width:80%;}
.skeleton-form{display:flex;flex-direction:column;gap:8px;max-width:640px;}
.skeleton-form > .skeleton-bar{display:block;width:30%;height:10px;margin-top:8px;}
.skeleton-form > .skeleton-bar:first-child{margin-top:0;}
.skeleton-form > .skeleton-box{height:36px;}
.skeleton-form > .skeleton-form__button{width:120px;height:34px;margin-top:10px;}
.skeleton-canvas > .skeleton-bar{width:240px;height:14px;margin:4px 0 12px;}
.skeleton-canvas > .skeleton-box{height:520px;}
.skeleton-week{display:grid;grid-template-columns:160px repeat(7,1fr);gap:8px;padding:12px;}
.skeleton-week--month{grid-template-columns:repeat(7,1fr);}
.skeleton-week > .skeleton-bar{width:60%;height:10px;}
.skeleton-week > .skeleton-box{height:38px;}
.skeleton-week--month > .skeleton-box{height:72px;}
.skeleton-panels{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-bottom:14px;}
.skeleton-panels > .skeleton-box{height:240px;}
@media (prefers-reduced-motion:reduce){.skeleton-bar,.skeleton-box,.skeleton-shape{animation:none;}}

/* ── Hidden ──────────────────────────────────────────────────────────────
   The hidden attribute hides, even on an element whose fixture class sets
   display (.btn, .card, .field). Not !important: a script that shows an
   element with style.display still wins, which several pages rely on. */
[hidden]{display:none;}

/* A tab group that wears blue instead of the brand orange (the Snow area).
   Put `accent-blue` on the group's wrapper (block tab_group_class in
   core/tab_group_base.html); everything drawn from --accent follows. */
.accent-blue{--accent:var(--blue);--accent-d:var(--blue-d);--accent-weak:var(--blue-weak);--brand:var(--blue);--brand-dk:var(--blue-d);}
/* Captured evidence (work/_evidence.html): videos play inline, photos are
   thumbnails that open full size. */
.evidence{display:flex;flex-wrap:wrap;align-items:flex-start;gap:16px;}
.evidence__row{margin:0;max-width:480px;}
.evidence__caption{margin-top:4px;font-size:12px;color:var(--muted);}
.evidence__media{display:flex;flex-wrap:wrap;gap:8px;}
.evidence__video{width:360px;max-width:100%;height:200px;object-fit:contain;background:var(--surface);border-radius:var(--radius-sm);}
.evidence__thumb{display:block;width:120px;height:120px;object-fit:cover;border-radius:var(--radius-sm);}
/* A photographed document (a check, a bill): the whole page shown small, never cropped. */
.evidence__thumb--doc{width:180px;height:auto;max-height:120px;object-fit:contain;background:var(--card-2);border:1px solid var(--line);}
/* Which thing a workflow page shows (work/_kind_band.html): the template,
   the design every job follows, or one job running on it. */
.kind-band{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;padding:8px 14px;border-radius:var(--radius-sm);border:1px solid var(--line);font-size:13px;}
.kind-band__tag{font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;}
.kind-band--template{background:var(--teal-weak);border-color:var(--teal-line);}
.kind-band--template .kind-band__tag{color:var(--teal-ink);}
.kind-band--record{background:var(--amber-weak);border-color:var(--amber-line);}

/* ── Schedule board cleanup (2026-10-13) ─────────────────────────────────
   Fixture fixes found on Home > Schedule; kept together in this block.
   A filter bar written as <nav class="toolbar"> also matched base.css's
   old tab-bar rule for every <nav> (surface fill, bottom border), which drew
   a thin full-width band behind a few buttons. A toolbar is a bare row. */
nav.toolbar{background:none;border-bottom:0;}
/* A key-value list inside a table cell (the facts under a job's name): a
   narrow fixed label column, so every row's labels and values line up. */
.kv--compact{grid-template-columns:7.5rem minmax(0,1fr);gap:3px 12px;font-size:13px;line-height:1.4;}
.kv--compact dt{font-size:12px;}

/* ── Team picker and person chip ─────────────────────────────────────────
   apps.core.forms.TeamPicker (core/forms/widgets/team_picker.html,
   static/js/team_picker.js): the checkbox list a job's people are picked
   from, drawn as chips plus "+ Add a person" once the script runs. The
   person chip is also the boards' look for a person on a job. */
.team-picker__list{display:flex;flex-direction:column;gap:4px;}
.team-picker__option{display:flex;align-items:baseline;gap:6px;font-weight:500;cursor:pointer;}
.team-picker__meta{color:var(--muted);font-size:12px;font-weight:400;}
.team-picker__meta::before{content:"· ";}
.team-picker.is-enhanced{display:flex;flex-direction:column;gap:8px;}
.team-picker.is-enhanced .team-picker__list{display:none;}
.team-picker__chips{display:flex;flex-wrap:wrap;align-items:center;gap:6px;min-height:28px;}
.team-picker__empty{color:var(--muted);font-size:13px;}
.team-picker__add{max-width:22rem;}
.person-chip{display:inline-flex;align-items:center;gap:6px;padding:3px 4px 3px 3px;border:1px solid var(--border);border-radius:999px;background:var(--card);font-size:13px;font-weight:600;color:var(--text);}
.person-chip__av{display:inline-grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--accent);color:var(--on-accent);font-size:11px;font-weight:700;}
.person-chip__meta{color:var(--muted);font-weight:400;font-size:12px;}
.person-chip__x{display:inline-grid;place-items:center;width:20px;height:20px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--muted);font-size:14px;line-height:1;cursor:pointer;}
.person-chip__x:hover,.person-chip__x:focus-visible{background:var(--red);color:var(--on-accent);}

/* ── Person chip on a drag board ─────────────────────────────────────────
   The Cleaning board and Snow crews board (static/js/cleaning_board.js,
   snow_crews.js) drag a .person-chip from one spot to another; --drag gives
   it the grab cursor and stops touch scrolling and text selection mid-drag. */
.person-chip--drag{cursor:grab;touch-action:none;user-select:none;}
