/* ───────────────────────────────────────────────────────────────────────────
   Urban Makers — shared UI components
   Single source of truth for dashboard TABS, collapsible ACCORDIONS, the
   SHOPPING LIST, and the shared PRIMITIVES below (spinner, empty-state, button
   family). Edit here once; every page that loads this file stays in sync.
   The component canon (DESIGN-GUIDE.md §3) lives here: one implementation per
   role — buttons, the Save button, icon buttons, fields, labels, hints, tags,
   counts, dots — on the tokens in um-theme.css. Batch 1 of the guide's revision
   order (owner, 2026-09-22) deleted every page-local .btn-* / .icon-btn / .save-btn
   clone and the theme's force-paint of them, and every staff page loads this file.
   A page adds LAYOUT to these classes (flex, width, order), never a second look.
   Every control reads the text face, var(--um-font), never `inherit`: on a serif page (the wiki,
   body.um-serif) an inherited face drew the buttons in Source Serif (2026-09-24); the Grove pages
   map --um-font to their own face.
   ─────────────────────────────────────────────────────────────────────────── */

/* ── Shared primitives (spinner · empty-state · buttons) ───────────────────── */
/* One loading spinner: the active arc tracks currentColor, so it inherits the
   surrounding text colour; size it via width/height where a smaller inline one
   is needed (builder/punchlist keep a local size variant). */
.spinner{display:inline-block;width:28px;height:28px;border:3px solid rgba(0,0,0,.12);border-top-color:currentColor;border-radius:50%;animation:spin .7s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* One empty-state block (48 24, the guide's §3.5). */
.empty-state{text-align:center;padding:48px 24px;color:var(--ink-soft,#4A5A64)}

/* ── The component canon (DESIGN-GUIDE.md §3) — one implementation per role ──
   Heights, radii and sizes are the tokens (um-theme.css). Modifiers: .primary (ink
   fill) · .danger · .sm (28, in a row) · .lg (36, the page's one primary). A button's
   height is set, never left to padding; padding is horizontal only. */
.btn-primary,.btn-secondary,.btn-danger,.btn-ghost,.btn-sm,.save-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;margin:0;
  height:var(--um-h,32px);padding:0 12px;border:1px solid var(--um-line,#D8D5CC);border-radius:var(--um-radius-sm,4px);
  background:#fff;color:var(--um-ink,#1E2A32);font-family:var(--um-font,'IBM Plex Sans',sans-serif);font-size:var(--um-fs-ui,13px);font-weight:500;line-height:1;
  letter-spacing:.01em;text-transform:none;text-decoration:none;white-space:nowrap;cursor:pointer}
.btn-primary,.save-btn,.btn-sm.primary,.btn-secondary.primary{background:var(--um-ink,#1E2A32);border-color:var(--um-ink,#1E2A32);color:var(--um-paper,#FBFAF7)}
.btn-danger,.btn-sm.danger,.btn-secondary.danger{color:var(--um-danger,#B3432B);border-color:var(--um-danger,#B3432B)}
.btn-ghost{background:transparent;border-color:transparent;height:var(--um-h-sm,28px);padding:0 8px}
.btn-sm{height:var(--um-h-sm,28px);padding:0 8px}
.btn-lg,.btn-primary.lg,.btn-secondary.lg{height:var(--um-h-lg,36px);padding:0 16px}
.btn-secondary:hover,.btn-sm:hover,.btn-ghost:hover{background:var(--um-paper-mid,#F3F1EA)}
.btn-primary:hover,.save-btn:hover,.btn-sm.primary:hover,.btn-secondary.primary:hover{background:var(--um-ink,#1E2A32);opacity:.9}
.btn-danger:hover,.btn-sm.danger:hover,.btn-secondary.danger:hover{background:var(--um-danger-tint,#FBEEEA)}
.btn-primary:active,.btn-secondary:active,.btn-danger:active,.btn-ghost:active,.btn-sm:active,.save-btn:active{transform:translateY(1px)}
.btn-primary:disabled,.btn-secondary:disabled,.btn-danger:disabled,.btn-ghost:disabled,.btn-sm:disabled,.save-btn:disabled,
.btn-primary[aria-disabled=true],.btn-secondary[aria-disabled=true],.btn-sm[aria-disabled=true]{opacity:.45;cursor:default;pointer-events:none;transform:none}
/* The Save button (the bar's primary on the builder and the catalogue): a dot on the
   corner while there are unsaved changes, green for a moment after a save. The bar
   paints every child's colour with !important (um-theme), hence the !important here. */
.save-btn{position:relative}
.save-btn.dirty::after{content:"";position:absolute;top:-3px;right:-3px;width:8px;height:8px;border-radius:50%;background:var(--act,#E8630A);border:2px solid var(--um-ink,#1E2A32);z-index:1}
.save-btn.saved,.save-btn.saved:hover,.app-bar .save-btn.saved,.app-bar .save-btn.saved:hover{background:var(--um-success,#3E7C4F) !important;border-color:var(--um-success,#3E7C4F) !important;opacity:1}
/* Icon only: .icon-btn is the quiet 28 square in a row or a card (no border, hover
   paper-mid; .danger hovers red); .icon-sq (below, hub block) is the boxed 32 square
   for a toolbar. .np-x / .close is the dialog's × at 32, glyph 20. */
.icon-btn{display:inline-flex;align-items:center;justify-content:center;flex:none;box-sizing:border-box;width:var(--um-h-sm,28px);height:var(--um-h-sm,28px);padding:0;margin:0;border:0;border-radius:var(--um-radius-sm,4px);background:transparent;color:var(--ink-soft,#4A5A64);font-family:var(--um-font,'IBM Plex Sans',sans-serif);font-size:16px;line-height:1;cursor:pointer}
.icon-btn:hover{background:var(--um-paper-mid,#F3F1EA);color:var(--um-ink,#1E2A32)}
.icon-btn.danger:hover{background:var(--um-danger-tint,#FBEEEA);color:var(--um-danger,#B3432B)}
.icon-btn.np-x,.icon-btn.close{width:var(--um-h,32px);height:var(--um-h,32px);font-size:20px;font-weight:400}
/* The Lab's rail and phone tool menu (§2.3, the one 40 exception): the quiet icon button at 40, glyph 20. */
.icon-btn.tool{width:40px;height:40px;font-size:20px}
.icon-btn.tool svg{width:20px;height:20px}
/* Fields (DESIGN-GUIDE §3.2): ONE box for every input, select and textarea. The spec applies
   BY ELEMENT on every page that loads this file, at element specificity (0,0,1): it beats a page's
   `*{margin:0;padding:0}` reset (specificity 0, whatever its order — the first draft sat inside
   :where() and lost its padding to 26 of those resets) and still loses to any class rule, so a
   page that wants something else must say so with a class; batch 3 (2026-09-22) deleted the page
   rules, so the spec is what draws. The type exclusions sit inside :where() and add nothing.
   .um-field is the same spec as an explicit class (block, full width). .sm in a row, a table
   cell or a card foot (28). .err for a field in error (with a .um-hint.err under it). Focus is
   the border going ink plus the theme's global :focus-visible ring; no page adds a ring. */
input:not(:where([type=checkbox],[type=radio],[type=range],[type=file],[type=color],[type=submit],[type=button],[type=reset],[type=image],[type=hidden])),select,textarea,
.um-field{min-width:0;box-sizing:border-box;margin:0;height:var(--um-h,32px);padding:0 10px;border:1px solid var(--um-line,#D8D5CC);border-radius:var(--um-radius-sm,4px);background:#fff;color:var(--um-ink,#1E2A32);font-family:var(--um-font,'IBM Plex Sans',sans-serif);font-size:var(--um-fs-ui,13px);font-weight:500;line-height:1.2}
.um-field{display:block;width:100%}
textarea,textarea.um-field{height:auto;min-height:74px;padding:8px 10px;line-height:1.4;resize:vertical}
input.sm,select.sm,textarea.sm,.um-field.sm{height:var(--um-h-sm,28px);padding:0 8px}
textarea.sm,textarea.um-field.sm{height:auto;min-height:var(--um-h-sm,28px);padding:4px 8px}
input:focus,select:focus,textarea:focus,.um-field:focus{border-color:var(--um-ink,#1E2A32)}
input.err,select.err,textarea.err,.um-field.err{border-color:var(--um-danger,#B3432B)}
input:disabled,select:disabled,textarea:disabled,.um-field:disabled,input[readonly],textarea[readonly]{background:var(--um-paper-mid,#F3F1EA);color:var(--ink-soft,#4A5A64)}
input::placeholder,textarea::placeholder,.um-field::placeholder{color:var(--ink-soft,#4A5A64);opacity:.7}
input[type=checkbox],input[type=radio]{accent-color:var(--um-ink,#1E2A32);width:14px;height:14px;margin:0;vertical-align:middle}
input[type=range]{accent-color:var(--um-ink,#1E2A32)}
@media (max-width:600px){   /* a phone: 16px text (iOS never zooms back) and a touch-height box */
  input:not(:where([type=checkbox],[type=radio],[type=range],[type=file],[type=color],[type=submit],[type=button],[type=reset],[type=image],[type=hidden])),select,textarea,.um-field{font-size:16px;height:var(--um-h-lg,36px)}
  input.sm,select.sm,textarea.sm,.um-field.sm{height:var(--um-h,32px)}
  textarea,textarea.um-field,textarea.sm,textarea.um-field.sm{height:auto}
  textarea.sm,textarea.um-field.sm{min-height:var(--um-h,32px)}   /* sm is 32 on a phone, a row textarea too (a line's notes drew 30) */
}
/* A picker that reads as the field beside it (§3.2): a button in the field's box that opens a menu
   (umPickMenu), its value on the left, a chevron on the right; .empty shows the prompt in ink-soft.
   .sm 28 in a row. The shopping list's Type is one. */
.um-pick,.shop-type-btn{display:flex;align-items:center;gap:6px;box-sizing:border-box;min-width:0;margin:0;height:var(--um-h,32px);padding:0 10px;
  border:1px solid var(--um-line,#D8D5CC);border-radius:var(--um-radius-sm,4px);background:#fff;color:var(--um-ink,#1E2A32);font-family:var(--um-font,'IBM Plex Sans',sans-serif);
  font-size:var(--um-fs-ui,13px);font-weight:500;line-height:1.2;text-align:left;white-space:nowrap;cursor:pointer}
.um-pick .um-pick-v{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
.um-pick .um-ic{flex:none;color:var(--ink-soft,#4A5A64)}
.um-pick:hover,.um-pick:focus,.shop-type-btn:hover,.shop-type-btn:focus{border-color:var(--um-ink,#1E2A32)}
.um-pick.empty,.shop-type-btn.empty{color:var(--ink-soft,#4A5A64)}
.um-pick.sm,.shop-type-btn{height:var(--um-h-sm,28px);padding:0 8px}
.shop-type-btn{overflow:hidden;text-overflow:ellipsis}
.um-pick:disabled{background:var(--um-paper-mid,#F3F1EA);color:var(--ink-soft,#4A5A64);cursor:default}
.um-hint{margin-top:4px;font-size:var(--um-fs-meta,12px);line-height:1.4;color:var(--ink-soft,#4A5A64)}
.um-hint.err{color:var(--um-danger,#B3432B)}
/* Tags and kind labels (never pills: the 999 radius is a STATUS, .um-pill in um-theme). */
.um-tag{display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 8px;font-family:var(--um-font);border-radius:var(--um-radius-sm,4px);background:var(--um-paper-mid,#F3F1EA);color:var(--um-ink,#1E2A32);font-size:var(--um-fs-meta,12px);font-weight:500;line-height:1;white-space:nowrap}
.um-tag.kind{height:20px;padding:0 6px;font-size:var(--um-fs-label,11px);font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-soft,#4A5A64)}
/* Two kinds keep a hue (owner, 2026-09-24): optional is yellow ("otherwise hard to differentiate this pill with
   the 'area' pill") and provisional purple, the colour of the quotation's provisions box. The category tints,
   with a hairline inside. */
.um-tag.kind.opt{background:var(--cat-fin-tint);color:var(--cat-fin-ink);box-shadow:inset 0 0 0 1px var(--cat-fin-line)}
.um-tag.kind.prov{background:var(--cat-rep-tint);color:var(--cat-rep);box-shadow:inset 0 0 0 1px var(--cat-rep-line)}
/* A tag whose words can outrun its column (a room, a works type): they sit in .um-tag-t and end in an
   ellipsis inside the tag instead of running over the next cell. */
.um-tag:has(>.um-tag-t){max-width:100%}
.um-tag-t{min-width:0;overflow:hidden;text-overflow:ellipsis}
/* A quote line (quote-doc.js: the quotation, the builder's preview, the site review). Its name ends in its ref,
   small and quiet: a column of its own took more room than a code is worth (owner, 2026-09-25). Its labels sit on
   a line of their own under the name. */
.plt-name{overflow-wrap:anywhere}   /* a name with no space in it breaks inside its column, never over the next */
.plt-ref{font-family:var(--um-mono);font-size:var(--um-fs-label,11px);font-weight:500;color:var(--ink-soft,#4A5A64);white-space:nowrap}
.plt-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
/* The Amount says it in a word where a number would mislead (owner, 2026-09-25): FOC in green, a line that costs
   nothing "Included", an optional line's price in the optional label's yellow box (.um-tag.kind.opt) with a note
   under it in the same dark yellow. A struck price (a line discount) sits above the new amount, and "To be
   confirmed" may wrap: side by side they ran past the Amount column (the audit's sample quotation, 2026-09-24). */
.plt-foc{color:var(--um-success);font-weight:600}
.plt-incl{color:var(--ink-soft,#4A5A64);font-weight:500}
.plt-opt-note{padding-top:4px;text-align:right;font-size:var(--um-fs-meta,12px);font-style:italic;color:var(--cat-fin-ink)}
.plt-total-cell .price-strikethrough{display:block;margin-right:0}
.plt-total-cell .plt-cq-cell{white-space:normal}
.plt-photos{padding-top:4px;padding-bottom:4px}   /* a line's photos (quote-doc.js v16): .um-thumbs of .um-thumb, a tap opens them */
@media print{.plt-photos-row{display:none}}   /* the PDF stays as it was (owner, 2026-09-29) */
/* A line darkens a shade under the pointer (owner, 2026-09-25: "I want a mouse hover over a quotation row to
   'darken' slightly"). Its box runs 8 px past the text on both sides so the shade has room, and a clear ink layer
   darkens whatever the line sits on: white, or an optional line's paper. */
.pline{margin-left:-8px;margin-right:-8px;padding-left:8px;padding-right:8px}
@media (hover:hover){.pline:hover{background-image:linear-gradient(color-mix(in srgb,var(--um-ink) 5%,transparent),color-mix(in srgb,var(--um-ink) 5%,transparent))}}
/* A section's header is a filled band (owner, 2026-09-25: "make the psec-hdr row a bit more distinct - maybe a
   light fill across the row"), the table-header fill, 8 px past the text like its lines. It prints filled. */
.psec-hdr{background:var(--um-head-bg);margin-left:-8px;margin-right:-8px;padding-left:8px;padding-right:8px;-webkit-print-color-adjust:exact;print-color-adjust:exact}
/* A summary row's amount never breaks: on a phone "−$200.00" broke after its minus, a dash alone on a line. */
.srow>span:last-child{white-space:nowrap}
/* A quotation column under 640 px (a phone, or the column beside the plan): the figures take less room, so the
   name keeps its width. The quotation and the site review make their document box the qdoc container (the
   builder's preview does not: 640 px there leaves a name 300); print keeps the page's own layout. */
@media screen{
  @container qdoc (max-width:640px){
    .pline-table td.plt-qty-cell{width:56px;padding-left:4px}
    .pline-table td.plt-unit-cell{width:72px;padding-left:4px}
    .pline-table td.plt-total-cell{width:84px;padding-left:4px}
  }
}
.um-tag .x{border:0;background:none;padding:0;margin:0;font:inherit;font-size:12px;line-height:1;color:var(--ink-soft,#4A5A64);cursor:pointer}
/* A count bubble: mono, 18 high; .act only when it means act now. */
.um-count{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;box-sizing:border-box;border-radius:var(--um-radius-pill,999px);background:var(--um-paper-mid,#F3F1EA);color:var(--um-ink,#1E2A32);font-family:'IBM Plex Mono',ui-monospace,monospace;font-size:var(--um-fs-label,11px);font-weight:600;line-height:1}
.um-count.act{background:var(--act,#E8630A);color:var(--um-paper,#FBFAF7)}
/* A category is a 6px dot beside its name. */
.um-dot{display:inline-block;flex:none;width:6px;height:6px;border-radius:50%;background:var(--cat-tool,#4A5A64);vertical-align:middle}
.um-dot.fin{background:var(--cat-fin,#B08422)}.um-dot.exe{background:var(--cat-exe,#3E7C4F)}.um-dot.cli{background:var(--cat-cli,#2C5F8A)}.um-dot.rep{background:var(--cat-rep,#7A5C7E)}
.um-dot.ok{background:var(--um-success,#3E7C4F)}.um-dot.act{background:var(--act,#E8630A)}.um-dot.bad{background:var(--um-danger,#B3432B)}

/* ── The roles the pages drew for themselves (owner, 2026-09-24: "all modules connected to
   the app should be using elements from a master design stylesheet"). check-ui
   `page-restyle` now fails any page rule that gives a control a look, so a control the canon
   lacked is added HERE, once, and every page uses it. A page still sets its layout (width,
   margin, flex, order, position). Data reaches a look through a custom property, never a
   look property: <span class="who" style="--who:var(--who-3)">. ── */

/* Segmented control (§3.3): 2–6 exclusive options in one hairline box, hairline dividers, the
   chosen one ink. 32; .sm 28 in a row. .on is the chosen option (.active reads the same). The
   theme used to force-paint the face over six pages' own sizes. */
.seg{display:inline-flex;align-items:stretch;box-sizing:border-box;height:var(--um-h,32px);max-width:100%;margin:0;padding:0;vertical-align:middle;
  border:1px solid var(--um-line,#D8D5CC);border-radius:var(--um-radius-sm,4px);background:#fff;overflow:hidden}
.seg button{flex:1 1 auto;min-width:0;display:inline-flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;height:auto;margin:0;
  padding:0 12px;border:0;border-left:1px solid var(--um-line,#D8D5CC);border-radius:0;background:transparent;color:var(--ink-soft,#4A5A64);
  font-family:var(--um-font,'IBM Plex Sans',sans-serif);font-size:var(--um-fs-ui,13px);font-weight:500;line-height:1;letter-spacing:.01em;text-transform:none;white-space:nowrap;cursor:pointer}
.seg button:first-child{border-left:0}
.seg button:hover:not(.on):not(.active){background:var(--um-paper-mid,#F3F1EA);color:var(--um-ink,#1E2A32)}
.seg button.on,.seg button.active{background:var(--um-ink,#1E2A32);color:var(--um-paper,#FBFAF7)}
.seg button:disabled{opacity:.45;cursor:default}
.seg.sm{height:var(--um-h-sm,28px)}
.seg.sm button{padding:0 8px}
/* Six options in a panel (§3.3, #p-open-kind): a grid of segments; the 1-px gaps over the line colour
   draw the hairlines. The columns are the page's (grid-template-columns is layout). */
.seg.grid{display:inline-grid;height:auto;gap:1px;background:var(--um-line,#D8D5CC)}
.seg.grid button{height:var(--um-h,32px);border-left:0;background:#fff}
.seg.grid button.on,.seg.grid button.active{background:var(--um-ink,#1E2A32)}
.seg.grid.sm button{height:var(--um-h-sm,28px)}

/* Toggle chip and count-tab (§3.3): 28, 0 10, r4, 13/500 on a hairline; .on is ink. A count
   inside sits in 12 mono (.n). Not a filter: filtering is the ≡ Filter (CLAUDE.md rule 4). */
.fchip{display:inline-flex;align-items:center;gap:6px;box-sizing:border-box;height:var(--um-h-sm,28px);margin:0;padding:0 10px;
  border:1px solid var(--um-line,#D8D5CC);border-radius:var(--um-radius-sm,4px);background:#fff;color:var(--um-ink,#1E2A32);
  font-family:var(--um-font,'IBM Plex Sans',sans-serif);font-size:var(--um-fs-ui,13px);font-weight:500;line-height:1;letter-spacing:.01em;text-transform:none;white-space:nowrap;cursor:pointer}
.fchip:hover:not(.on){background:var(--um-paper-mid,#F3F1EA)}
.fchip.on{background:var(--um-ink,#1E2A32);border-color:var(--um-ink,#1E2A32);color:var(--um-paper,#FBFAF7)}
.fchip .n{font-family:var(--um-mono,'IBM Plex Mono',ui-monospace,monospace);font-size:var(--um-fs-meta,12px);font-weight:500;font-variant-numeric:tabular-nums;color:inherit;opacity:.75}
.fchip:disabled{opacity:.45;cursor:default}

/* A person (§3.4): a 22 circle in their fixed colour, initials 11/600. The colour is data:
   style="--who:var(--who-3)". A button when a tap tells more. */
.who{display:inline-flex;align-items:center;justify-content:center;flex:none;box-sizing:border-box;width:22px;height:22px;margin:0;padding:0;
  border:1px solid var(--um-line,#D8D5CC);border-radius:50%;background:var(--who,var(--who-1,#D4B3AF));color:var(--um-ink,#1E2A32);
  font-family:var(--um-font,'IBM Plex Sans',sans-serif);font-size:var(--um-fs-label,11px);font-weight:600;line-height:1;text-transform:uppercase;cursor:default}
button.who{cursor:pointer}
button.who:hover{border-color:var(--um-ink,#1E2A32)}

/* A text link that does something (§3.1): 13/500 in the link colour, no box, underlined on
   hover. For "Show more", "Clear", "Open in Xero": an action that is not the row's point. */
.btn-link{display:inline-flex;align-items:center;gap:6px;box-sizing:border-box;height:var(--um-h-sm,28px);margin:0;padding:0;border:0;background:none;
  color:var(--um-link,#2C5F8A);font-family:var(--um-font,'IBM Plex Sans',sans-serif);font-size:var(--um-fs-ui,13px);font-weight:500;line-height:1;text-align:inherit;text-decoration:none;
  white-space:nowrap;cursor:pointer}   /* 28 high: a row's height and a finger's target, one spec wherever it stands */
.btn-link:hover{text-decoration:underline;text-underline-offset:2px}
.btn-link.act{color:var(--um-warn-ink,#BC5008)}   /* the one action on an act-now line (the hub's needs-you row) */
.btn-link.danger{color:var(--um-danger,#B3432B)}
.btn-link:disabled{opacity:.45;cursor:default;text-decoration:none}

/* Act now (§2.6): orange means act now and nothing else, so an orange button is the one thing a
   client (or a designer) should press next: Accept, Sign, Pay, Send. .act on .btn-primary or
   .btn-sm; with .lg it is the client document's one CTA (§3.7). */
.btn-primary.act,.btn-sm.act{background:var(--act,#E8630A);border-color:var(--act,#E8630A);color:#fff}
.btn-primary.act:hover,.btn-sm.act:hover{background:var(--act,#E8630A);opacity:.9}

/* A menu a page builds itself (§3.5; a picker is umPickMenu, a field's list is ui-kit's): r8,
   hairline, the menu shadow, padding 4; items 32 (28 .dense), 0 10, r4, 13/500, left-aligned;
   hover paper-mid; .danger red; .on 600 with a tick, like ui-kit's list. Where it sits
   (position, top, left, z-index) is the page's. */
.um-menu{box-sizing:border-box;min-width:160px;max-width:min(340px,calc(100vw - 16px));padding:4px;background:#fff;border:1px solid var(--um-line,#D8D5CC);
  border-radius:var(--um-radius,8px);box-shadow:var(--um-shadow-menu,0 4px 12px -4px rgba(30,42,50,.18));color:var(--um-ink,#1E2A32);
  font-family:var(--um-font,inherit);font-size:var(--um-fs-ui,13px)}
.um-menu>button,.um-menu .um-menu-it{position:relative;display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;height:var(--um-h,32px);margin:0;
  padding:0 10px;border:0;border-radius:var(--um-radius-sm,4px);background:transparent;color:var(--um-ink,#1E2A32);font-family:var(--um-font,'IBM Plex Sans',sans-serif);
  font-size:var(--um-fs-ui,13px);font-weight:500;line-height:1;text-align:left;text-decoration:none;white-space:nowrap;cursor:pointer}
.um-menu>button:hover,.um-menu .um-menu-it:hover{background:var(--um-paper-mid,#F3F1EA)}
.um-menu>button.danger,.um-menu .um-menu-it.danger{color:var(--um-danger,#B3432B)}
.um-menu>button.on,.um-menu .um-menu-it.on{font-weight:600;padding-right:28px}
.um-menu>button.on::after,.um-menu .um-menu-it.on::after{content:"\2713";position:absolute;right:10px;top:50%;transform:translateY(-50%);color:var(--ink-soft,#4A5A64);font-weight:400}
.um-menu>button:disabled,.um-menu .um-menu-it:disabled{opacity:.45;cursor:default}
.um-menu.dense>button,.um-menu.dense .um-menu-it{height:var(--um-h-sm,28px)}
.um-menu>button.two,.um-menu .um-menu-it.two{flex-direction:column;align-items:flex-start;justify-content:center;gap:2px;height:auto;min-height:var(--um-h,32px);
  padding:6px 10px;line-height:1.3;white-space:normal}   /* an item with a sub-line under its label */
.um-menu-sub{font-size:var(--um-fs-meta,12px);font-weight:400;color:var(--ink-soft,#4A5A64)}
.um-menu-sep{height:1px;margin:4px 0;background:var(--um-line,#D8D5CC)}
.um-menu-head{padding:8px 10px 4px;font-size:var(--um-fs-label,11px);font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-soft,#4A5A64)}

/* A row that is a button (§3.5): the question of an FAQ, a group's head that opens it. Full width,
   48 at least, 12 16, 14/500 ink, left-aligned, wraps; hover paper-mid. A row, not a control, so it
   takes the row's height, not a button's. */
.um-rowbtn{display:flex;align-items:center;gap:12px;box-sizing:border-box;width:100%;min-height:48px;margin:0;padding:12px 16px;border:0;
  border-radius:var(--um-radius-sm,4px);background:transparent;color:var(--um-ink,#1E2A32);font-family:var(--um-font,'IBM Plex Sans',sans-serif);font-size:var(--um-fs-body,14px);
  font-weight:500;line-height:1.4;text-align:left;text-decoration:none;white-space:normal;cursor:pointer}
.um-rowbtn:hover{background:var(--um-paper-mid,#F3F1EA)}


/* A photo you tap to open (§3.5): a 48 square on paper-mid, the picture as data (style="--img:url(…)"),
   a 16 glyph centred when there is none; hover border ink. Media, so a thumbnail's size, not a
   control's height. */
.um-thumb{display:inline-flex;align-items:center;justify-content:center;flex:none;box-sizing:border-box;width:48px;height:48px;margin:0;padding:0;
  border:1px solid var(--um-line,#D8D5CC);border-radius:var(--um-radius-sm,4px);background:var(--img,none) center/cover no-repeat,var(--um-paper-mid,#F3F1EA);
  color:var(--ink-soft,#4A5A64);font-size:16px;line-height:1;cursor:pointer}
.um-thumb:hover{border-color:var(--um-ink,#1E2A32)}

/* Money typed into a column (§3.2 + §2.1): the field, in mono, tabular, right-aligned. */
input.money{font-family:var(--um-mono,'IBM Plex Mono',ui-monospace,monospace);font-variant-numeric:tabular-nums;text-align:right}
input.money.neg{color:var(--um-danger,#B3432B)}   /* money that comes off (a deduction), as a negative figure reads in a ledger */

/* A tool in hand, a mode that is on (§3.1): the pressed button is ink, as the chosen segment and
   the chip are. With aria-pressed. The builder's plan tool and the Lab's toolbar drew their own. */
.btn-secondary.on,.btn-sm.on,.btn-ghost.on,.icon-sq.on,.icon-btn.on{background:var(--um-ink,#1E2A32);border-color:var(--um-ink,#1E2A32);color:var(--um-paper,#FBFAF7)}
.btn-secondary.on:hover,.btn-sm.on:hover,.btn-ghost.on:hover,.icon-sq.on:hover,.icon-btn.on:hover{background:var(--um-ink,#1E2A32);color:var(--um-paper,#FBFAF7);opacity:.9}

/* A finger's target round a small control, without changing how it looks: .um-hit grows the tap
   area 6 px each way (a 32 square takes a 44 tap). Layout only; the look stays the canon's. */
.um-hit{position:relative}
.um-hit::before{content:"";position:absolute;inset:-6px}

/* A colour to pick (§3.2): a 28 square (a row control) showing its colour, which is data: style="--sw:#C9A27E".
   The chosen one carries an ink ring. The builder's plan tool and the Lab drew four of these. */
.um-swatch{display:inline-block;flex:none;box-sizing:border-box;width:var(--um-h-sm,28px);height:var(--um-h-sm,28px);margin:0;padding:0;border:1px solid var(--um-line,#D8D5CC);
  border-radius:var(--um-radius-sm,4px);background:var(--sw,#fff);cursor:pointer;vertical-align:middle}
.um-swatch:hover{border-color:var(--um-ink,#1E2A32)}
.um-swatch.on{border-color:var(--um-ink,#1E2A32);outline:2px solid var(--um-ink,#1E2A32);outline-offset:1px}

/* A control floating over a canvas or a photo (zoom, the draft controls): the boxed 32 square
   with the menu shadow, the one shadow a control may carry, so it reads off the drawing. */
.icon-sq.float,.btn-secondary.float,.seg.float{box-shadow:var(--um-shadow-menu,0 4px 12px -4px rgba(30,42,50,.18))}
/* …and the row-size one over a photo thumbnail (make cover, remove): the 28 square, boxed, white. */
.icon-btn.float{background:#fff;border:1px solid var(--um-line,#D8D5CC);box-shadow:var(--um-shadow-menu,0 4px 12px -4px rgba(30,42,50,.18))}
.icon-btn.float:hover{background:var(--um-paper-mid,#F3F1EA);color:var(--um-ink,#1E2A32)}
.icon-btn.float.danger:hover{background:var(--um-danger-tint,#FBEEEA);color:var(--um-danger,#B3432B)}

/* Photo markup (photo-markup.js, §3.5, 2026-09-29): a full-screen editor over the page. A paper bar at the
   top (close, title, undo, redo, the caller's ⋯, Save) and at the foot (the tools, then the inks, fill,
   size and delete), the photo between on ink so it reads. Its controls are the canon's; the stage takes
   every touch (pinch and pan are the editor's, not the page's). */
.um-pm{position:fixed;inset:0;z-index:8800;display:flex;flex-direction:column;background:var(--um-paper,#FBFAF7);color:var(--um-ink,#1E2A32);font-family:var(--um-font,'IBM Plex Sans',sans-serif);font-size:var(--um-fs-body,14px)}
.um-pm-top,.um-pm-row{display:flex;align-items:center;gap:8px;box-sizing:border-box;padding:8px 12px}
.um-pm-top{border-bottom:1px solid var(--um-line,#D8D5CC);padding-top:calc(8px + env(safe-area-inset-top,0px))}
.um-pm-title{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--um-fs-lead,16px);font-weight:500}
.um-pm-sp,.um-pm-gap{flex:1 1 auto;min-width:8px}
.um-pm-note{box-sizing:border-box;padding:8px 12px;border-bottom:1px solid var(--um-line,#D8D5CC);font-size:var(--um-fs-meta,12px);color:var(--ink-soft,#4A5A64)}
.um-pm-stage{position:relative;flex:1 1 auto;min-height:0;overflow:hidden;background:var(--um-ink,#1E2A32);color:var(--um-paper,#FBFAF7);touch-action:none;user-select:none;-webkit-user-select:none}
.um-pm-stage canvas{position:absolute;inset:0;display:block;width:100%;height:100%}
.um-pm-stage .spinner{position:absolute;left:50%;top:50%;margin:-14px 0 0 -14px}
.um-pm-fit{position:absolute;right:12px;bottom:12px}
.um-pm-body{flex:1 1 auto;min-height:0;overflow:auto;display:flex;flex-direction:column;gap:12px;box-sizing:border-box;padding:16px 16px calc(16px + env(safe-area-inset-bottom,0px))}
.um-pm-body .spinner{align-self:center}
.um-pm-fit[hidden]{display:none}
.um-pm-bar{border-top:1px solid var(--um-line,#D8D5CC);padding-bottom:env(safe-area-inset-bottom,0px)}
.um-pm-row{overflow-x:auto;scrollbar-width:none}
.um-pm-row::-webkit-scrollbar{display:none}
.um-pm-row+.um-pm-row{padding-top:0}
.um-pm-tools{justify-content:center}
/* A phone: the nine tools share the width, none scrolled out of sight (Select sits first). */
@media (max-width:600px){ .um-pm-tools{justify-content:space-between;gap:0;padding-left:4px;padding-right:4px} }
body.um-pm-open{overflow:hidden}

/* A photo in a gallery (§3.5, 2026-09-29: Site photos and a quote line's picker): a square filling its grid
   cell, the picture cover on paper-mid, r4, hairline; a tap opens it, or picks it (.on: an ink ring and a
   tick). Glyph badges in its corners (.um-photo-b: a WhatsApp internal group top left, picked top right,
   marked up bottom left); a video shows the play glyph. .um-photos is the grid, a day's photos under a
   .um-label with its count. Media, so a tile's size, not a control's height. */
.um-photo-day{margin:0 0 16px}
.um-photo-day>.um-label{display:flex;align-items:center;gap:8px;margin:0 0 8px}
.um-photos{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px}
.um-photo{position:relative;display:block;box-sizing:border-box;width:100%;aspect-ratio:1/1;margin:0;padding:0;overflow:hidden;
  border:1px solid var(--um-line,#D8D5CC);border-radius:var(--um-radius-sm,4px);background:var(--um-paper-mid,#F3F1EA);
  color:var(--ink-soft,#4A5A64);font-family:var(--um-font,'IBM Plex Sans',sans-serif);cursor:pointer}
.um-photo:hover{border-color:var(--um-ink,#1E2A32)}
.um-photo.on{border-color:var(--um-ink,#1E2A32);outline:2px solid var(--um-ink,#1E2A32);outline-offset:1px}
.um-photo img,.um-photo video{display:block;width:100%;height:100%;object-fit:cover}
.um-photo-b{position:absolute;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:var(--um-radius-sm,4px);
  background:var(--um-paper,#FBFAF7);color:var(--um-ink,#1E2A32);box-shadow:var(--um-shadow-menu,0 4px 12px -4px rgba(30,42,50,.18));pointer-events:none}
.um-photo-b.tl{top:4px;left:4px}
.um-photo-b.tr{top:4px;right:4px;background:var(--um-ink,#1E2A32);color:var(--um-paper,#FBFAF7)}
.um-photo-b.bl{bottom:4px;left:4px}
.um-photo-play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff;pointer-events:none}
.um-photo-play>svg{box-sizing:content-box;padding:8px;border-radius:var(--um-radius-pill,999px);background:color-mix(in srgb,var(--um-ink,#1E2A32) 60%,transparent)}   /* a dark disc: the glyph reads on a frame or on none */
@media (max-width:600px){ .um-photos{grid-template-columns:repeat(auto-fill,minmax(88px,1fr))} }

/* A line's photos (a quote line in the builder, the quotation's row under a line): the thumbs in a row,
   wrapping, 8 apart; the builder's ends in the Add photo button. */
.um-thumbs{display:flex;flex-wrap:wrap;align-items:center;gap:8px}

/* ── Tabs (group the section accordions) ── */
#dash-tabs{display:flex;gap:8px;flex-wrap:wrap;margin:6px 0 14px;border-bottom:1px solid var(--um-line);padding-bottom:10px}
.dtab{display:inline-flex;align-items:center;height:var(--um-h);padding:0 12px;background:#fff;border:1px solid var(--um-line);border-radius:var(--um-radius-sm);color:var(--ink-soft);font-size:var(--um-fs-ui);font-weight:500;cursor:pointer;white-space:nowrap}
.dtab:hover{border-color:var(--um-line);background:var(--um-paper)}
.dtab.active{background:var(--um-ink);border-color:var(--um-ink);color:#fff}
.dash-tab-pane{display:none}
.dash-tab-pane.active{display:block}

/* ── Accordion sections ── */
.acc-toolbar{display:flex;justify-content:flex-end;margin-bottom:10px}
.acc-toggle-all{display:inline-flex;align-items:center;height:var(--um-h-sm);padding:0 8px;background:none;border:1px solid var(--um-line);border-radius:var(--um-radius-sm);color:var(--ink-soft);font-size:var(--um-fs-ui);font-weight:500;cursor:pointer}
.acc-toggle-all:hover{background:var(--um-paper-mid);color:var(--um-ink)}
.acc-section{background:#fff;border:1px solid var(--um-line);border-radius:var(--um-radius-card,8px);margin-bottom:12px}
.acc-head{display:flex;align-items:center;gap:10px;padding:14px 18px;cursor:pointer;user-select:none;border-radius:var(--um-radius-card,8px)}
.acc-head:hover{background:var(--um-paper-mid)}
.acc-head .section-title{margin:0}
.acc-caret{font-size:11px;color:var(--ink-soft);flex-shrink:0;width:12px;text-align:center;transition:transform .15s}
.acc-section:not(.collapsed) .acc-caret{transform:rotate(90deg)}
.section-spacer{flex:1}
.acc-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:flex-end}
.acc-body{padding:0 18px 18px}
.acc-section.collapsed .acc-body{display:none}

/* ── Shopping list ── */
.shop-area{border:1px solid var(--um-line);border-radius:var(--um-radius-card,8px);margin-bottom:10px;overflow:hidden}
.shop-area-head{display:flex;align-items:center;gap:9px;padding:11px 14px;cursor:pointer;user-select:none;background:var(--um-paper-mid)}
.shop-area-head:hover{background:var(--um-paper-mid)}
.shop-area-head .acc-caret{transition:transform .15s}
.shop-area.open .shop-area-head .acc-caret{transform:rotate(90deg)}
.shop-area-title{font-weight:700;font-size:14px;flex:1}
.shop-area-del{border:none;background:none;color:var(--ink-soft);font-size:14px;line-height:1;cursor:pointer;padding:4px 6px;border-radius:var(--um-radius-sm)}
.shop-area-del:hover{color:var(--um-danger);background:var(--um-danger-tint)}
.shop-area-body{padding:10px 14px 14px}
.shop-area:not(.open) .shop-area-body{display:none}
.shop-table{display:flex;flex-direction:column;gap:7px;margin-bottom:8px}
.shop-row{display:grid;grid-template-columns:auto 1.4fr 1.2fr 1fr 132px 1fr auto auto auto;gap:7px;align-items:center}
.shop-row input,.shop-row select{width:100%;min-width:0}   /* the box is the field spec; the elements carry .sm (a row) */
.shop-typewrap{display:flex;gap:5px;min-width:0}
/* Type is a branded picker, not a system dropdown — it has to read like the
   inputs beside it. */
.shop-type-btn{flex:1}   /* the look is .um-pick's (a picker that reads as a field), at row size */
.shop-grip{cursor:grab;color:var(--um-line);font-size:14px;line-height:1;padding:0 2px;user-select:none;flex-shrink:0}
.shop-grip:hover{color:var(--ink-soft)}
.shop-area-grip{cursor:grab}
.shop-area-ren{border:none;background:none;color:var(--ink-soft);font-size:13px;line-height:1;cursor:pointer;padding:4px 6px;border-radius:var(--um-radius-sm)}
.shop-area-ren:hover{color:var(--um-ink);background:var(--um-paper-dark)}
/* Past its delivery date and still not bought — the one state worth a colour. */
.shop-row.shop-late .shop-due{border-color:var(--um-danger);color:var(--um-danger);font-weight:600}
.sortable-ghost{opacity:.45}
.shop-status{display:inline-flex;align-items:center;height:var(--um-h-sm);padding:0 10px;font-size:var(--um-fs-ui);font-weight:500;border:1px solid var(--um-line);border-radius:var(--um-radius-sm);background:#fff;white-space:nowrap;cursor:pointer}
.shop-status.purchased{background:var(--cat-exe-tint);border-color:var(--cat-exe-line);color:var(--um-success)}
.shop-open,.shop-del{display:inline-flex;align-items:center;justify-content:center;height:var(--um-h-sm);padding:0 8px;border:1px solid var(--um-line);border-radius:var(--um-radius-sm);background:#fff;font-size:var(--um-fs-ui);cursor:pointer;text-decoration:none;color:var(--ink-soft);line-height:1}
.shop-del:hover{background:var(--um-danger-tint);border-color:var(--um-danger-tint);color:var(--um-danger)}
.shop-additem{margin-top:2px}

/* ── Mobile ── */
@media(max-width:768px){
  .acc-head{flex-wrap:wrap}
  .acc-head .section-spacer{display:none}
  .acc-head .section-title{flex:1;flex-basis:auto;min-width:0}
  .acc-actions{width:100%;justify-content:flex-start;margin-top:8px}
  .acc-section.collapsed .acc-head .acc-actions,
  .acc-section.collapsed .acc-head #report-status{display:none}
  .acc-body{padding:0 14px 14px}
}
/* Shopping rows stack so the editable fields stay usable on phones AND the
   560–768px tablet/large-phone range, where the 6-column grid is too cramped. */
@media(max-width:768px){
  .shop-row{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
  .shop-row>input,.shop-typewrap{flex:1 1 100%}
  /* Size and delivery date share a row — both are short, and pairing them is
     how they read on a product page anyway. */
  .shop-row .shop-dims{flex:1 1 55%}
  .shop-row .shop-due{flex:1 1 38%}
  .shop-status{flex:1 1 auto}
  .shop-grip,.shop-open,.shop-del{flex:0 0 auto}
}

/* ── Project cost tracker (reuses .shop-area group chrome) ── */
.cost-pl{font-size:12px;color:var(--ink-soft);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cost-pl b{color:var(--um-ink)}
.cost-pl .neg,.cost-grand .neg{color:var(--um-danger)}
/* Four columns: receipts live in Xero, and the vendor moved up to the scope
   header (project_vendors) — a row is tick | description | amount | ✕. A payee
   that came in on an imported bill renders as a small line under the
   description (.cost-payee), not as a column. */
.cost-head-row{display:grid;grid-template-columns:24px 1fr 110px 30px;gap:7px;font-size:var(--um-fs-label,11px);font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-soft);padding:0 2px 4px}
.cost-row{grid-template-columns:24px 1fr 110px 30px}
.cost-desc{display:flex;flex-direction:column;gap:2px;min-width:0}
.cost-desc input{width:100%}
.cost-payee{font-size:var(--um-fs-meta,12px);color:var(--ink-soft);padding-left:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* The tick that makes a pile of rows one thing you can move or delete. */
.cost-tick{display:flex;align-items:center;justify-content:center;cursor:pointer;flex:0 0 auto}
.cost-tick input{cursor:pointer;margin:0}
.cost-row.is-sel{background:var(--cat-fin-tint);border-color:var(--cat-fin-line)}
/* Sticky, because the rows being acted on are the ones scrolled past it. Dark,
   because it is a mode: something is selected and two of these buttons are
   destructive. Colours are mixed from the tokens rather than picked, so the
   light-on-dark variants cannot drift from --um-success/--um-warn. */
/* The selection bar (§3.5, 2026-09-24: the Cost section's and the boards' were two): ink, paper
   text, what is ticked and what to do with it. Its buttons are the canon's, repainted for the dark:
   paper 16% face, 32% border; .primary is the one action, paper filled. Where it sits (sticky,
   fixed, full width) is the page's; .edge squares it against the screen's edge. */
.um-selbar,.cost-selbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;box-sizing:border-box;padding:10px 14px;
  background:var(--um-ink);color:var(--um-paper);border-radius:var(--um-radius-card,8px);font-size:var(--um-fs-ui,13px);box-shadow:var(--um-shadow-dialog)}
.um-selbar.edge{border-radius:0}
.um-selbar b,.cost-selbar b{color:var(--um-paper)}
.um-selbar .btn-sm,.um-selbar .btn-secondary,.cost-selbar .btn-sm{background:color-mix(in srgb,var(--um-paper) 16%,transparent);
  border-color:color-mix(in srgb,var(--um-paper) 32%,transparent);color:var(--um-paper)}
.um-selbar .btn-sm:hover,.um-selbar .btn-secondary:hover,.cost-selbar .btn-sm:hover{background:color-mix(in srgb,var(--um-paper) 28%,transparent)}
.um-selbar .btn-sm.primary,.um-selbar .btn-secondary.primary,.cost-selbar .btn-sm.primary{background:var(--um-paper);border-color:var(--um-paper);color:var(--um-ink)}
.um-selbar .btn-sm.primary:hover,.um-selbar .btn-secondary.primary:hover,.cost-selbar .btn-sm.primary:hover{background:var(--um-paper);opacity:.9}
.cost-selbar{position:sticky;bottom:0;z-index:3;margin-top:10px}
.cost-selbar .csb-sum{font-weight:700}
.cost-selbar .csb-note,.cost-selbar .csb-ok,.cost-selbar .csb-gap{font-size:12px}
.cost-selbar .csb-note{color:color-mix(in srgb,var(--um-paper) 78%,var(--um-ink))}
.cost-selbar .csb-ok{color:color-mix(in srgb,var(--um-success) 42%,var(--um-paper))}
.cost-selbar .csb-gap{color:color-mix(in srgb,var(--um-warn) 52%,var(--um-paper))}
.cost-receipts{display:flex;align-items:center;gap:5px;flex-wrap:wrap;min-width:0}
/* A receipt chip is a .um-tag (batch 2); .cost-chip keeps only its layout: a long file name is clipped. */
.cost-chip{max-width:150px;overflow:hidden}
.cost-chip a{cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:inherit;text-decoration:none}
.cost-chip a.x{flex-shrink:0}
.cost-attach{display:inline-flex;align-items:center;height:var(--um-h-sm);border:1px dashed var(--um-line);border-radius:var(--um-radius-sm);background:var(--um-paper);padding:0 8px;font-size:var(--um-fs-meta);cursor:pointer;line-height:1}
.cost-attach:hover{background:var(--um-paper-mid)}
.cost-grand{display:flex;gap:18px;align-items:baseline;justify-content:flex-end;flex-wrap:wrap;background:#fff;border:1px solid var(--um-line);border-radius:var(--um-radius-card,8px);padding:12px 16px;margin-top:4px;font-size:13px;color:var(--ink-soft)}
.cost-grand b{font-size:var(--um-fs-ui,13px);font-weight:500;color:var(--um-ink)}
@media(max-width:768px){
  .cost-head-row{display:none}
  /* Stacked, but explicitly placed rather than left to wrap — costs are keyed in
     on a phone, and a wrapped ✕ ended up on a line of its own reading as if it
     belonged to whatever followed it. Children are, in DOM order:
     tick, description (.cost-desc), amount, ✕. */
  .cost-row{display:grid;grid-template-columns:24px 1fr 42px;column-gap:6px;row-gap:6px;align-items:center}
  .cost-row>.cost-tick{grid-area:1/1}
  .cost-row>.cost-desc{grid-area:1/2}
  .cost-row>.shop-del{grid-area:1/3;justify-self:end}
  .cost-row>input{grid-area:2/2/auto/-1}                 /* amount */
  .cost-pl{white-space:normal}
}

/* ── Receipt inbox (Telegram bot → cost tracker) ── */
.cost-inbox{border:1px solid var(--cat-fin-line);background:var(--cat-fin-tint);border-radius:var(--um-radius-card,8px);margin-bottom:12px;overflow:hidden}
.cost-inbox-head{display:flex;align-items:center;gap:9px;padding:11px 14px;cursor:pointer;user-select:none;font-size:13px}
.cost-inbox-head .acc-caret{transition:transform .15s}
.cost-inbox.open .cost-inbox-head .acc-caret{transform:rotate(90deg)}
.cost-inbox-body{padding:4px 14px 12px;display:flex;flex-direction:column;gap:8px}
.cost-inbox-row{display:flex;align-items:center;gap:9px;background:#fff;border:1px solid var(--cat-fin-line);border-radius:8px;padding:8px 10px;flex-wrap:wrap}
.cost-inbox-meta{flex:1;min-width:180px;display:flex;flex-direction:column;font-size:var(--um-fs-ui,13px);line-height:1.4}
.cost-inbox-meta span{font-size:var(--um-fs-meta,12px);color:var(--ink-soft)}
.cost-inbox-cat{max-width:170px}   /* the field spec + .sm on the element */
.cost-inbox-row .btn-sm{background:var(--um-ink);border-color:var(--um-ink);color:var(--um-paper)}   /* the one action in an inbox row is filled */

/* ───────────────────────────────────────────────────────────────────────────
   Hub tile dashboard — shared by projects.html (designer) and portal.html
   (client). Tiles replace the tab+accordion navigation on those two pages.
   Category accents are deliberately desaturated to sit with the paper/sage/
   beige brand palette. All --um-* references carry hex fallbacks because
   catalogue.html loads this file without those page tokens.
   ─────────────────────────────────────────────────────────────────────────── */
/* Category and action tokens are defined once in um-theme.css :root (loaded
   last); the .hub-cat-* classes below map them onto --accent/--tint/--line. */
.hub-cat-fin{--accent:var(--cat-fin);--tint:var(--cat-fin-tint);--line:var(--cat-fin-line)}
.hub-cat-exe{--accent:var(--cat-exe);--tint:var(--cat-exe-tint);--line:var(--cat-exe-line)}
.hub-cat-cli{--accent:var(--cat-cli);--tint:var(--cat-cli-tint);--line:var(--cat-cli-line)}
.hub-cat-rep{--accent:var(--cat-rep);--tint:var(--cat-rep-tint);--line:var(--cat-rep-line)}
.hub-cat-tool{--accent:var(--cat-tool);--tint:var(--cat-tool-tint);--line:var(--cat-tool-line)}

/* Section label bands */
/* ── Project hub, direction B "Ledger" (owner pick, 2026-09-09) ──────────
   A section label with a hairline, then rows: 16px stroke icon · name · what
   is in it · a count or state on the right. No cards, no tints, no coloured
   edges — the category colour is the 6px dot on the band. The same markup
   reads on a phone and on a desktop (two columns of bands). No sub-line under
   the name (owner, 2026-09-12). Hooks: data-hub-badge (Approved / Draft …) and
   data-hub-pip — one phrase that says what its number is ("31 items"). */
.hub-band{display:flex;align-items:center;gap:10px;margin:22px 0 4px}
.hub-band h2{font-family:var(--um-font);font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-soft);margin:0}
.hub-band .hub-hint{display:none}
.hub-band .hub-rule{flex:1;height:1px;background:var(--um-paper-dark,var(--um-paper-mid))}
.hub-legdot{width:6px;height:6px;border-radius:50%;display:inline-block;margin-right:6px;vertical-align:1px;background:var(--accent)}
.hub-rows{display:flex;flex-direction:column}   /* the band's rule is the line above the first row — no second one */
.hub-cats{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 40px;align-items:start}
.hub-row{display:grid;grid-template-columns:20px minmax(0,1fr) fit-content(50%);align-items:center;gap:12px;min-height:42px;padding:4px 0 4px 6px;border-bottom:1px solid var(--um-paper-mid);cursor:pointer;color:inherit;font:inherit;text-align:left;width:100%;background:transparent;border-left:none;border-right:none;border-top:none;box-sizing:border-box}
.hub-row:hover{background:#fff}
.hub-row:focus-visible{outline:2px solid var(--um-ink);outline-offset:-2px}
.hub-row .ico{display:flex;color:var(--ink-soft)}
.hub-row .main{display:flex;align-items:baseline;gap:8px;min-width:0;flex-wrap:wrap}
.hub-row .t{font-size:13px;font-weight:500;color:var(--um-ink);white-space:nowrap}
.hub-row .s{font-size:12px;color:var(--ink-soft);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The status side takes half the row at most and ends in an ellipsis: sized to its words, a long one pushed the row's
   name out of its column and on a phone ran 449 px past the screen (the portal's timeline row, owner 2026-09-25). A
   phrase belongs under the name (.s), not here. */
.hub-row .end{display:flex;align-items:center;gap:10px;justify-content:flex-end;min-width:0}
.hub-row .badge{font-size:12px;font-weight:500;color:var(--accent);white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis}
.hub-row .badge.act{color:var(--um-warn-ink,#BC5008)}
.hub-row .badge.ok{color:var(--um-success)}
.hub-pip{font-family:'IBM Plex Mono',ui-monospace,monospace;font-size:12px;color:var(--ink-soft);min-width:18px;text-align:right;font-variant-numeric:tabular-nums}
.hub-pip.dim{color:var(--um-paper-dark)}
.hub-pip.warn{color:var(--um-warn-ink,#BC5008);font-weight:600}
.hub-pip.ok{color:var(--um-success)}
/* the needs-you line: one orange dot, one sentence, the action at the end */
.hub-need{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--um-warn-ink,#BC5008);margin:0 0 6px;padding:10px 6px;border-bottom:1px solid var(--um-paper-dark)}
.hub-need::before{content:'';width:8px;height:8px;border-radius:50%;background:var(--act);flex:none}
.hub-need .go{margin-left:auto}   /* the canon .btn-link.act: it was a 16-px underlined word */
/* The tinted pill belongs to the portal's TILES only. On a hub row the pip is
   the bare mono count (owner, 2026-09-11: "a background behind the 0, offset
   to the right" — these three rules had outlived the tiles they were for). */
.hub-tile .hub-pip.warn{background:var(--act-tint);color:var(--um-warn-ink,#BC5008)}
.hub-tile .hub-pip.ok{background:var(--cat-exe-tint);color:var(--um-success)}
.hub-tile .hub-pip.dim{background:var(--um-paper-mid,var(--um-paper-mid));color:var(--ink-soft)}
.hub-row .hub-pip{background:none;padding:0;border-radius:0;letter-spacing:0;font-weight:500;font-family:var(--um-font);min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}   /* a phrase, so the page's face, not mono */
.hub-row .hub-pip.warn{font-weight:600}

/* Drill-in back bar */
.hub-backbar{display:flex;align-items:center;gap:10px;margin:0 0 14px}
/* ← is the whole button (owner, 2026-09-12: no need to spell "All sections"). */
.hub-back-btn{background:#fff;border:1px solid var(--um-line);border-radius:4px;color:var(--um-ink);cursor:pointer;width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;flex:none;font-family:var(--um-font,'IBM Plex Sans',sans-serif)}
.hub-back-btn:hover{background:var(--um-paper-mid);border-color:var(--um-line)}
/* The section switcher: on a phone the sibling chips scrolled sideways; one
   button naming the section opens the whole list instead. Desk keeps chips. */
.hub-jump{display:none;align-items:center;gap:6px;height:32px;padding:0 12px;border:1px solid var(--um-line);border-radius:4px;background:#fff;color:var(--um-ink);font-size:13px;font-weight:500;cursor:pointer;font-family:var(--um-font,'IBM Plex Sans',sans-serif);min-width:0;max-width:100%}
.hub-jump .hub-jump-l{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@media(max-width:768px){.hub-jump{display:inline-flex}.hub-sibs{display:none}}
.hub-backbar .hub-crumb{font-size:13px;color:var(--ink-soft);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* Condensed project header (designer) */
.phead{background:#fff;border:1px solid var(--um-paper-dark,var(--um-line));border-radius:var(--um-radius-card,8px);padding:14px 18px;margin-bottom:18px}
.phead-row{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.phead-name{font-size:var(--um-fs-title,26px);font-weight:600;line-height:1.25}
.phead-client{font-size:13px;color:var(--ink-soft);margin-top:2px}
.phead-sub{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.phead-stage{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.phead-spacer{flex:1}
/* Phone (owner, 2026-09-12: "the top part takes up half the screen"): the name,
   then client · ID on one line, then stage and the actions on ONE row; the
   "no approved quotation" notice goes (the Quotations row says Draft/Approved). */
@media(max-width:560px){
  .phead{padding:10px 12px;margin-bottom:10px}
  .phead-row{gap:6px 8px}
  .phead-id{flex:1 1 100%;min-width:0}
  .phead-name{font-size:16px;line-height:1.2}
  .phead-client{margin-top:0}
  .phead-sub .pcode.is-head{margin-top:0;font-size:11px;padding:2px 7px}
  .phead-stage{flex:1 1 0;min-width:0;gap:6px;flex-wrap:nowrap}   /* takes what the actions leave; the select gives, never the row */
  .phead-stage .pf-label{display:none}
  .phead-stage .dash-stage-select{flex:1 1 0;min-width:0;max-width:150px}
  .phead-stage .won-toggle{font-size:11px;padding:4px 9px}
  .phead-spacer{display:none}
  .phead-actions{margin-left:auto;gap:6px}
  /* (the header's square action button on a phone is projects.html's layout since 2026-09-24: a width of
     36 at the canon padding; a padding of 0 here made a button spec no other page drew) */
  .phead-actions .btn-secondary .chev{display:none}
  #dash-budget-box{flex:1 1 100%;order:5}
  #dash-budget-box .no-approved-box{display:none}
  #dash-budget-box .approved-budget-box{padding:4px 10px}
}
/* A square icon button (32px, one stroke icon, the label in title/aria-label) —
   a toolbar's actions without words; .primary is the one filled action. */
.icon-sq{width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--um-line);border-radius:4px;background:#fff;color:var(--um-ink);cursor:pointer;font-family:var(--um-font,'IBM Plex Sans',sans-serif);flex:none}
.icon-sq:hover{background:var(--um-paper-mid)}
.icon-sq.primary{background:var(--um-ink);border-color:var(--um-ink);color:var(--um-paper)}
.icon-sq.primary:hover{background:var(--um-ink);opacity:.92}
.details-grid{display:none;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:12px 20px;margin-top:14px;padding-top:14px;border-top:1px solid var(--um-paper-mid,var(--um-line))}
.details-grid.open{display:grid}
.chev{transition:transform .18s;display:inline-block}
.chev.open{transform:rotate(180deg)}

/* Client welcome header + needs-you banner (portal) */
.hub-welcome{background:#fff;border:1px solid var(--um-paper-dark,var(--um-line));border-radius:var(--um-radius-card,8px);padding:18px 20px;margin-bottom:20px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.hub-welcome .hi{font-size:13px;color:var(--ink-soft);font-weight:600}
.hub-welcome h1{font-size:var(--um-fs-title,26px);font-weight:600;line-height:1.25;margin:2px 0 3px}
@media (max-width:600px){.hub-welcome h1{font-size:var(--um-fs-h2,20px)}}
.hub-welcome .sub{font-size:13px;color:var(--ink-soft)}
.hub-welcome .hub-spacer{flex:1}
.contract-mini{text-align:right}
.contract-mini .lbl{font-size:11px;color:var(--ink-soft);text-transform:uppercase;letter-spacing:.5px}
.contract-mini .amt{font-size:20px;font-weight:700;color:var(--um-ink,var(--um-ink))}

/* Hub responsive */
@media(max-width:560px){
  .hub-cats{grid-template-columns:1fr;gap:0}
  .hub-row .main{flex-direction:column;gap:1px;align-items:flex-start}
  .hub-row .t{white-space:normal}
  .hub-row .s{max-width:100%}   /* stacked under the name it sizes to its words: capped, it ends in its ellipsis */
  .hub-band{margin:18px 2px 10px}
}

/* ── The plan in a frame (the Lab, read-only: fp3d-lab.html?embed=1&client=1) ──────────
   Drawn by quote-view.html (beside the quotation) and portal.html (the Floor plan section),
   so it lives here (owner, 2026-09-23: the client pans and zooms the plan itself, not a
   picture of it). A head — the title, or a tab per sheet, and the buttons — over the frame.
   .full is the full-screen sheet: pinned over the page, the Close button showing, and
   body.um-plan-full stops the page scrolling behind it. A page adds LAYOUT only: the
   quotation's split and sticky column, the portal's 70 vh height. */
.um-plan{display:flex;flex-direction:column;min-width:0;background:#fff;border:1px solid var(--um-line,#D8D5CC);border-radius:var(--um-radius-card);overflow:hidden}
.um-plan-head{display:flex;align-items:center;gap:8px;flex:none;min-height:36px;padding:4px 8px 4px 12px;border-bottom:1px solid var(--um-line,#D8D5CC);background:var(--um-paper,#FBFAF7)}
.um-plan-title{min-width:0;font-size:var(--um-fs-ui);font-weight:500;color:var(--um-ink,#1E2A32);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.um-plan-tabs{display:flex;align-items:center;gap:4px;flex-wrap:wrap;min-width:0}
.um-plan-tabs [aria-selected="true"]{background:var(--um-ink,#1E2A32);border-color:var(--um-ink,#1E2A32);color:var(--um-paper,#FBFAF7)}
.um-plan-spacer{flex:1}
.um-plan-close{display:none}
.um-plan-frame{display:block;flex:1;width:100%;min-height:0;border:0;background:var(--um-paper,#FBFAF7)}
.um-plan.full{position:fixed;inset:0;z-index:960;margin:0;border:0;border-radius:0}
.um-plan.full .um-plan-open{display:none}
.um-plan.full .um-plan-close{display:inline-flex}
.um-plan.full .um-plan-frame{flex:1;height:auto;min-height:0}
body.um-plan-full{overflow:hidden}

/* ── The designer's reply under a client's question ──────────
   Drawn by portal.html (Your requests & our replies) and quote-view.html (under the client's comment on a line,
   2026-10-06), so it lives here: the reply's text and photos, a caps label naming it and its date. */
.qr-a{margin-top:8px;padding:8px 12px;background:var(--cat-cli-tint);border-left:3px solid var(--um-link);border-radius:var(--um-radius-card);font-size:var(--um-fs-body);color:var(--ink-soft);line-height:1.55}
.qr-a-lbl{display:block;font-size:var(--um-fs-label);font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--um-link);margin-bottom:4px}
.qr-thumbs{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
.qr-thumb{width:64px;height:64px;object-fit:cover;border-radius:var(--um-radius-card);border:1px solid var(--cat-cli-line);cursor:pointer}

/* A phone (≤ 600): every control a step up, as the fields already are (§3.2; §7 line 7 "controls at
   36"): 32 → 36, 28 → 32. A finger's target, and a button the height of the field beside it. The
   Lab's 40 tool, a row's 48 and a pill stay as they are. It is the LAST block in this file on purpose:
   a step written above a role's base rule loses to it at the same specificity (the .icon-sq base sits
   in the hub block, and a phone kept drawing it at 32). */
@media (max-width:600px){
  .btn-primary,.btn-secondary,.btn-danger,.save-btn,.seg,.um-pick{height:var(--um-h-lg,36px)}
  .icon-sq{width:var(--um-h-lg,36px);height:var(--um-h-lg,36px)}
  .btn-sm,.btn-ghost,.btn-link,.fchip,.seg.sm,.um-pick.sm,.shop-type-btn{height:var(--um-h,32px)}
  .icon-btn{width:var(--um-h,32px);height:var(--um-h,32px)}
  .icon-btn.np-x,.icon-btn.close{width:var(--um-h-lg,36px);height:var(--um-h-lg,36px)}
  .icon-btn.tool{width:40px;height:40px}
  .seg.grid{height:auto}
  .seg.grid button{height:var(--um-h-lg,36px)}
  .seg.grid.sm button{height:var(--um-h,32px)}
  .um-menu>button,.um-menu .um-menu-it{height:var(--um-h-lg,36px)}
  .um-menu.dense>button,.um-menu.dense .um-menu-it{height:var(--um-h,32px)}
  .um-pick,.shop-type-btn{font-size:16px}
}
