
.progress-bar{background:var(--panel2);border:1px solid var(--border);height:22px;overflow:hidden;margin:1rem 0}
.progress-fill{background:var(--primary);height:100%;transition:width .6s cubic-bezier(.4,0,.2,1);display:flex;align-items:center;padding-left:10px;font-size:.72rem;font-weight:600;color:#fff;white-space:nowrap;box-shadow:0 0 12px rgba(63,185,80,.35)}
.preset-btn{display:inline-block;margin:.25rem .25rem 0 0;padding:.4rem .8rem;border:1px solid var(--border2);cursor:pointer;font-size:.82rem;color:var(--text);background:var(--panel2);transition:border-color .12s ease,background .12s ease,color .12s ease}
.preset-btn:hover{background:var(--row-hover);border-color:var(--input-border-hover)}
.preset-btn.active{border-color:var(--green-hi);background:rgba(35,134,54,.18);color:var(--green-soft)}
.rule-form{margin:1rem 0;padding:1rem;border:1px solid var(--border2);background:var(--panel2)}
.status-ok{color:var(--green-soft)}
.status-warn{color:#f0883e;font-weight:600}
.status-exceed{color:#f85149;font-weight:600}
.status-approach{color:#f0883e;font-weight:600}
.status-green{color:var(--green-soft)}
.status-yellow{color:#d29922}
.status-red{color:#f85149}
.substance-picker{position:relative}
.substance-dropdown{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--th-bg);border:1px solid var(--border2);max-height:14rem;overflow-y:auto;z-index:20;display:none;box-shadow:0 12px 30px rgba(0,0,0,.5)}
.substance-dropdown.show{display:block}
.substance-option{padding:.5rem .8rem;cursor:pointer;color:var(--text);font-size:.86rem;transition:background .1s ease}
.substance-option:hover{background:rgba(46,160,67,.16)}
.substance-option .label{color:var(--muted);font-size:.74rem;margin-left:.4rem}
.autocomplete-picker{position:relative}
.autocomplete-dropdown{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--th-bg);border:1px solid var(--border2);max-height:14rem;overflow-y:auto;z-index:20;display:none;box-shadow:0 12px 30px rgba(0,0,0,.5)}
.autocomplete-option{padding:.5rem .8rem;cursor:pointer;color:var(--text);font-size:.86rem;transition:background .1s ease}
.autocomplete-option:hover{background:rgba(46,160,67,.16)}
.exp-pill{display:inline-block;margin-left:.4rem;font-size:.7rem;font-weight:600;color:#a371f7;border:1px solid #a371f7;padding:.02rem .45rem;text-decoration:none;vertical-align:middle;white-space:nowrap}
.exp-pill:hover{background:rgba(163,113,247,.14)}
/* KEPT on top of the house 480px block, per the owner's ruling: taper has six
   nav tabs and this is what fits them on one phone row. Everything the house
   block already provides — main padding WITH safe-area insets, h1 sizing, .nav
   gap, .card and .modal-content padding — is deliberately NOT repeated, because
   repeating it is how taper lost the safe-area insets in the first place. */
@media(max-width:480px){.exp-pill .exp-lbl{display:none}.exp-pill{margin-left:.3rem;padding:.05rem .35rem}}:root{--menu-radius:8px;--r:9px;--r-ctl:var(--r);--r-card:12px;--r-pill:999px}:where(button,[class*="btn"],input:not([type=checkbox]):not([type=radio]),
  select,textarea,pre,code,[class*="chip"],summary){border-radius:var(--r-ctl)}
/* "-row" is here for the same reason the two tbody-tr selectors are: a hoverable
   LIST ROW is a surface, and it only looks like one while hovered — which is
   exactly when a square corner next to round controls reads as a mistake. It was
   already named twice for the two ListView variants; the pattern generalises it so
   a hand-rolled row inherits like a hand-rolled card does. Checked against the real
   class names rather than guessed: field-row, xfn-row, ts-row, al-row, cat-row,
   plan-row, ds-row, planned-row … the ones with no background and no full border
   (field-row, lv-rowdata) take the radius as a no-op, so there is nothing to
   regress. 856 of these were the last square things on mcp.zm.is/capabilities. */
/* A CALLOUT is a surface too: a bordered block with a background that says
   something about the content next to it. There is no shared component for one —
   every plug writes its own — so the role has to catch it by name, and the naming
   turns out to be remarkably consistent across 103 distinct class names in the
   fleet: note / notice / callout / banner / warn / alert. The ones that are plain
   text rather than a block (.hint, .q-note) have no background and no border, so
   the radius is a no-op on them. "notice" needs its own pattern — it does not
   contain "note".
   A bordered <details> is a disclosure PANEL, matched by tag because it is
   routinely written with no class at all (tooleditor's permissions preview, which
   is what showed this gap on mcp.zm.is/capabilities). Its <summary> already takes
   the control radius above. */
:where(.card,[class*="-card"],[class*="card-"],table,.empty,.modal-content,.nav-tabs,
  [class*="stat"],details,
  [class*="note"],[class*="notice"],[class*="callout"],[class*="banner"],[class*="warn"],[class*="alert"],
  [class*="panel"],[class*="tile"],[class*="-row"],.tbl-stack tbody tr,.lv-cards tbody tr){border-radius:var(--r-card)}
/* Pills (filter chips) stay fully round. BADGES take the house radius so they match
   the listview they sit inside — owner's experiment this pass; revert by moving
   [class*="badge"] back into the pill rule above. */
:where(.pill,[class*="pill"]){border-radius:var(--r-pill)}
:where([class*="badge"]){border-radius:var(--r)}
/* A badge is one token: it must NEVER wrap between its icon and its label. Owner:
   "they should never break up the icon and the text". Measured on ideas: the
   src-badge "retrospective" rendered 96x39 across two lines with the emoji stranded
   on the first. Role-matched like the radius, so every badge in the mesh inherits it
   and no plug has to remember. inline-flex (not just nowrap) also keeps the glyph and
   text on a shared baseline when the badge sits in a wrapping row. */
:where(.pill,[class*="pill"],[class*="badge"]){display:inline-flex;align-items:center;gap:.3em;white-space:nowrap}
/* Icon-only controls are SQUARE IN SHAPE — equal sides, via the shared component's
   width/height — and then take the house radius like everything else. Owner: "the
   modals should be square with rounded corners, so the lengths of their sides are
   equal". I first read "make the modal icons square" as square-CORNERED and set
   border-radius:0 here; the point was the opposite. A 35x30 rounded rectangle looks
   wrong ("small rectangles dont look great with curved edges"), so the fix is equal
   sides, not sharp corners. webui.IconButton already sizes them 2rem x 2rem. */
/* Menus and popovers take the dedicated --menu-radius the drop shapes already use
   (MenuCSS, .sg-dd), so every floating surface agrees. menu-pop is the role class;
   [class*="menu"] alone would also catch table cells like .lv-colmenu-col. */
:where([class*="menu-pop"],[class*="dropdown"],[class*="popover"]){border-radius:var(--menu-radius,6px)}
/* Floating labels/tooltips are chips, not panels — they take the control radius.
   lbl/tip are the naming already in use (flowweb's .fb-slbl speed-dial label).
   NOT "-lbl": fb-slbl contains no such substring, which is exactly the trap of
   guessing a pattern instead of checking it against the real class. */
:where([class*="tooltip"],[class*="lbl"],[class*="-tip"]){border-radius:var(--r-ctl)}

/* Surfaces: bordered blocks with a background. */
:where(.cal-cell,.rule-form,.progress-bar){border-radius:var(--r-card)}
/* The /trends usage bars: a track with a fill clipped inside it, so only the
   TRACK is named — the fill inherits the shape through overflow:hidden, and
   rounding it separately would round its leading edge mid-track. */
:where(.trend-bar){border-radius:var(--r-ctl)}
/* The legend swatch stands in for a cal-cell, so it wears the cell's corner. */
:where(.cal-swatch){border-radius:var(--r)}
/* The day-cell hover readout floats over the grid — a popover, not a panel. */
:where(.cal-detail){border-radius:var(--menu-radius,6px)}
/* The progress FILL is a segment of the bar it sits in: rounding it separately
   would round the leading edge mid-track. The bar already clips (overflow:hidden),
   so it inherits the container's corners and keeps a square leading edge. */

/* The period switch and the substance selector share ONE row: the switch is 390px
   of an 860px column, so a second row for a single field was a row spent on
   nothing. The selector takes the free space to the right and both controls sit on
   the same centre line, which is the alignment the owner asked for — a grid rather
   than two blocks that happen to follow each other.
   Below 40rem they stack, because three period tabs plus a field do not fit a
   phone and the switch already stretches its tabs to full width there. */
.trend-controls{display:flex;gap:var(--sp-3);align-items:center;flex-wrap:wrap;margin-bottom:var(--sp-5)}
.trend-controls .view-toggle{flex:0 0 auto;margin:0}
.trend-filter{display:flex;gap:var(--sp-2);align-items:center;margin:0;flex:1 1 16rem;min-width:0;justify-content:flex-end}
.trend-filter .substance-picker{flex:0 1 22rem;min-width:0}
@media(max-width:40rem){
  .trend-controls{flex-direction:column;align-items:stretch;gap:var(--sp-2)}
  .trend-filter{justify-content:stretch}
  .trend-filter .substance-picker{flex:1 1 auto}
  /* FLEX-BASIS FOLLOWS THE AXIS, AND THE AXIS JUST FLIPPED.
     The desktop rule is flex:1 1 16rem, which in the ROW container above is a 16rem
     WIDTH basis — right, and what keeps the picker a sensible size beside the tabs.
     The line directly above turns the container into a COLUMN, so that same 16rem
     becomes a HEIGHT basis: a 256px-tall form wrapped around a 34px input, which
     align-items:center then centres, leaving ~110px of nothing above it and ~110px
     below. That is the owner's "empty spaces here", and it is most of it — the two
     --sp-5 margins are 24px between them.
     Reset to content height. Not by editing the desktop rule: 16rem is correct there,
     on the axis it was written for. */
  .trend-filter{flex:0 0 auto}
  /* THE CONTROLS AND THE CHART EACH RESERVE --sp-5, AND ON A PHONE THAT IS THE
     LARGEST GAP ON THE PAGE. The mobile rule above already stacks the controls into
     three full-width rows; leaving both 1.5rem margins on top of that is what the
     owner boxed as "empty spaces here" at a 360px viewport. Adjacent margins collapse
     to the larger, so this is one 0.75rem gap, not two. Desktop keeps --sp-5, where
     the controls sit on ONE row and the space is doing real separating work. */
  .trend-controls{margin-bottom:var(--sp-3)}
  .trend-chart{margin-top:var(--sp-3)}
}
.trend-chart{margin:var(--sp-5) 0}
.trend-chart h3{margin:0 0 var(--sp-3)}
.trend-rows{display:flex;flex-direction:column;gap:var(--sp-1);margin-bottom:var(--sp-2)}
.trend-row{display:flex;align-items:center;gap:var(--sp-2)}
.trend-date{flex:0 0 2.6rem;font-size:.72rem;color:var(--muted);text-align:right;white-space:nowrap}
/* THE TRACK IS MOSTLY EMPTY AND THE NUMBER WAS ONLY IN A title= TOOLTIP, which a
   phone has no way to show. The fill is honestly proportional — one busy day sets the
   scale and the rest sit near a third of the width — so the empty part is not a bug to
   squash, it is space with nothing in it. The value goes there. That is the second
   half of the owner's "empty spaces here".
   overflow stays visible so a label past a long bar is not clipped; the fill keeps its
   own rounding. */
.trend-bar{flex:1;background:var(--row-hover);height:14px;position:relative}
.trend-val{position:absolute;top:0;line-height:14px;padding-left:.4rem;font-size:.68rem;
  color:var(--muted);white-space:nowrap;pointer-events:none}
.trend-fill{height:100%;min-width:2px;transition:width .3s ease}
select{background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%238b949e' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");background-repeat:no-repeat;background-position:right .7rem center;padding-right:2rem}.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2,.5rem);min-height:var(--control-h);padding:.4rem .9rem;font-weight:500;font-size:.84rem;letter-spacing:.03em;font-family:inherit;line-height:1;white-space:nowrap;border:1px solid var(--border2);background:var(--panel2);color:var(--text-hi);cursor:pointer;text-decoration:none;transition:background .13s ease,border-color .13s ease,box-shadow .13s ease,color .13s ease}
.btn:hover{text-decoration:none;border-color:var(--text-hi);background:var(--panel)}
.btn:focus-visible{outline:1px solid var(--primary);outline-offset:2px}
.btn.primary{background:transparent;color:var(--primary);border-color:var(--primary)}
.btn.primary:hover{background:var(--primary);color:#06210f;box-shadow:0 0 0 1px var(--primary),0 0 14px -3px var(--primary)}
.btn.secondary{background:transparent;color:var(--muted);border-color:var(--border2)}
.btn.secondary:hover{background:var(--panel);color:var(--text-hi);border-color:var(--text-hi)}
.btn.danger{background:transparent;color:var(--red);border-color:var(--danger)}
.btn.danger:hover{background:var(--danger);color:#fff;box-shadow:0 0 14px -3px var(--danger)}
/* .action-bar makes its CONTROLS compact — the caller cannot get the size wrong,
   because the container decides it. ONE declaration, listing the button AND the field:
   they are the same size because they are literally the same rule, not because two
   rules agree today.
   Owner review: "buttons need to be the same size as field". min-height alone was not
   enough — measured, the select came out 37.6px against the buttons' 30.4px, because
   its own padding (8.8px) and font-size (15.2px) set a natural height above the floor.
   A control shares a button's BOX or it does not match it.
   The input selector repeats the :not()s from FormControlsCSS deliberately: that rule
   is (0,2,1) and would otherwise keep the full-height floor here. */
.btn.sm,.action-bar .btn,.action-bar select,.action-bar input:not([type=checkbox]):not([type=radio]){min-height:var(--control-h-sm);padding:.28rem .6rem;font-size:.78rem}
.btn-icon{padding:.5rem;width:var(--control-h-icon);height:var(--control-h-icon);min-height:var(--control-h-icon);justify-content:center}
.btn-icon.sm{padding:.32rem;width:var(--control-h-icon-sm);height:var(--control-h-icon-sm);min-height:var(--control-h-icon-sm)}
.btn-icon>i{margin:0}
/* ---- DISABLED: the one control state the mesh never drew.
   Measured before this rule existed: a disabled button rendered PIXEL-IDENTICAL to an
   enabled one, cursor:pointer included, and repainted on hover like a live control. The
   only way to find out it was disabled was to press it and watch nothing happen. That
   is an affordance bug, not a missing nicety, and it was fleet-wide — three components
   had each invented a local opacity (.fb-btn .55, .fb-an-tool .4, .tgl .45) precisely
   because there was nothing to inherit.
   It quiets the SURFACE and dims the LABEL rather than putting opacity on the element.
   That is .lv-page.lv-disabled's lesson, already written down two files away: opacity
   fades the border and the background too, by different visible amounts, so a control
   whose whole point is to read as one box reads as three pieces.
   var(--panel) is deliberate over "keep whatever background the variant had": a
   disabled .primary must stop being green, and resolving per-variant would need one
   rule per variant that all say the same thing.
   The :hover twin is REQUIRED, not decorative. The generic button:hover baseline is
   (0,2,0) and .btn.danger:hover is (0,3,0), so without a (0,3,0) rule placed after
   them a disabled control still lights up under the cursor — which is the exact "this
   is pressable" signal being removed. aria-disabled is carried alongside :disabled
   because an <a class="btn"> cannot be :disabled at all, and a link styled as a button
   is the shape this system exists to make identical. */
button:disabled,.btn:disabled,button[aria-disabled="true"],.btn[aria-disabled="true"],
button:disabled:hover,.btn:disabled:hover,button[aria-disabled="true"]:hover,.btn[aria-disabled="true"]:hover{
  background:var(--panel);color:var(--faint);border-color:var(--border);
  cursor:default;box-shadow:none;filter:none;text-decoration:none}
.nav{display:flex;gap:var(--sp-2,.5rem);margin:1.5rem 0;flex-wrap:wrap}
.nav .btn{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-2,.5rem);flex:1 1 5rem;min-width:4.6rem;background:var(--card,#13181f);color:var(--text,#c9d1d9);font-weight:600;border:1px solid var(--border2,#30363d);padding:.85rem .5rem;font-size:.74rem;text-align:center;text-decoration:none;transition:background .13s ease,border-color .13s ease,color .13s ease}
.nav .btn .ico{font-size:1.55rem;line-height:1}
.nav .btn:hover{background:var(--panel,#1c2230);border-color:var(--primary,#3fb950);color:var(--text-hi,#fff);text-decoration:none}
.nav .btn:active{background:var(--panel2,#10151c)}
.nav .btn:focus-visible{outline:2px solid #3fb950;outline-offset:2px}
.nav .btn.primary{background:var(--primary);border-color:rgba(0,0,0,.18);color:#06210f}
.nav .btn.primary:hover{filter:brightness(1.08);background:var(--primary);border-color:rgba(0,0,0,.18)}

.view-toggle,.set-tabs{display:inline-flex;flex-wrap:wrap;gap:var(--sp-1,.25rem);margin:.2rem 0 1rem;border:1px solid var(--border2);padding:.2rem;background:var(--panel2);border-radius:calc(var(--r) + .2rem)}
.view-toggle .vt-btn,.set-tabs .set-tab{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2,.5rem);min-height:0;padding:.35rem .8rem;font-size:.85rem;font-weight:600;font-family:inherit;color:var(--muted);text-decoration:none;background:transparent;border:0;cursor:pointer;transition:background .12s ease,color .12s ease}
.view-toggle .vt-btn:hover,.set-tabs .set-tab:hover{color:var(--text);background:transparent}
.view-toggle .vt-btn.vt-active,.set-tabs .set-tab.active{background:var(--panel);color:var(--link)}
@media (max-width:480px){.view-toggle.vt-fill{display:flex;flex-wrap:nowrap;width:100%}.view-toggle.vt-fill .vt-btn{flex:1 1 0;padding:.35rem .4rem;font-size:.8rem;min-width:0}}
.dose-form-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--sp-4,1rem)}
@media(max-width:480px){.dose-form-grid{grid-template-columns:minmax(0,1fr)}}button.danger,button.delete,button.del,.btn-danger{background:var(--danger,#da3633);color:#fff;border:1px solid rgba(0,0,0,.18)}
button.danger:hover,button.delete:hover,button.del:hover,.btn-danger:hover{filter:none;background:#f85149;box-shadow:0 3px 10px rgba(218,54,51,.3)}
/* Compact inline delete (dense table rows): a bare red trash/✕ glyph, no box. */
button.del-icon,.del-icon{background:none;border:none;color:var(--red,#f85149);box-shadow:none;cursor:pointer;font-weight:600;font-size:1rem;padding:.1rem .4rem;line-height:1}
button.del-icon:hover,.del-icon:hover{filter:none;background:none;color:#ff7b72;box-shadow:none}:root[data-theme="light"]{--bg:#ffffff;--panel:#f6f8fa;--panel2:#ffffff;--border:#d0d7de;--border2:#d8dee4;--text:#1f2328;--text-hi:#0f1419;--muted:#4a535e;--faint:#59626d;--green:#1a7f37;--green-hi:#1f883d;--green-soft:#1a7f37;--blue:#0969da;--red:#cf222e;--primary:#1f883d;--primary-strong:#1a7f37;--danger:#cf222e;--danger-strong:#a40e26;--link:#0969da;--accent:var(--primary);--card:#ffffff;--th-bg:#f6f8fa;--row-hover:#f3f4f6;--code-bg:#eff1f3;--header-bg:rgba(255,255,255,.85);--input-border:#d0d7de;--input-border-hover:#afb8c1;--body-bg:#ffffff}
:root[data-theme="light"] body{color:var(--text)}.theme-toggle{display:none!important}.hdr-util{display:inline-flex;align-items:center;gap:var(--sp-1,.25rem);flex:none}
.hdr-util:empty{display:none}
.hdr-btn{background:transparent;border:1px solid var(--border2);color:var(--muted);width:var(--control-h-icon);height:var(--control-h-icon);min-height:var(--control-h-icon);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:0;font-size:.9rem;flex:none;text-decoration:none;transition:color .12s ease,border-color .12s ease,background .12s ease}
.hdr-btn:hover{color:var(--text-hi);border-color:var(--text-hi);background:var(--panel);text-decoration:none}
.hdr-btn.active{color:var(--primary);border-color:var(--primary)}
.hdr-btn>i{margin:0}