/* Ported once from docs/prototypes/house-dashboard-variant-a.html. That file is a
   frozen browser-only mock -- the evidence for
   docs/adr/20260817211024-house-dashboard-flat-bottom-tabs.md -- and it is not kept
   in step with this stylesheet in either direction. It shows what was decided on
   2026-08-17; re-syncing it to today's CSS would erase exactly that. Where the two
   differ, this file is authoritative.

   What still stands there and is deliberately gone here: `--nav-h: 66px`, `#app`
   at `100vh`, and `.scroller`'s `padding-bottom: calc(var(--nav-h) +
   env(safe-area-inset-bottom) + 20px)`. The mock overlays an absolutely positioned
   `.nav` on a full-bleed `.scroller`, so it has to reserve the bar's height by
   hand. This shell is a flex column at `100svh` with `.nav` as an in-flow item
   that occupies its own space, so there is nothing to reserve: `--nav-h` has no
   consumer left and `.scroller` pads a plain 20px. `.nav`'s `padding-bottom:
   env(safe-area-inset-bottom)` is the one rule of that group that survived
   unchanged. Also ours alone: `overflow: hidden` on `html, body`, and the `.lede`
   and `.meal-add` groups -- the mock has no auth and no forms. The one-line
   inventory row (`.inv`) and its classes are ours too: `.nm` for the name,
   `.loc` for the location pill, `.add` for the Stock-to-Buy control on `low` and
   `out` rows, `.x` for delete, and `.rm` / `.keep` for the two-tap delete
   question. The mock still groups by location and draws a
   two-line row; it never had these rules. The `.stock-add`, `.stock-expiry`
   and `.inv .exp` groups were ours alone too and all went on 2026-08-19: the
   first two with the Stock add form, which left no form on that screen to lay
   out, and the third with the row's expiry, which is deferred to the next
   version. The mock draws an expiry too, but as a second chip off the same
   `chip()` helper as the state -- it never had a rule of this name.

   Prototype-debug chrome (#state, #statebtn) was omitted at the port and never
   added. Meter and stepper rules were ported and are kept unused on purpose --
   the MVP replaced par levels with a three-value stock state, so there is no
   ratio to draw -- and stock_test.go asserts neither reaches the page. That
   choice stands even though par levels are superseded: the rules record what
   the port looked like and the test pins their absence. `.steps`, `.inline`
   and `--mono` are unreferenced too. The resync this file once owed the mock
   was settled by freezing rather than re-syncing: a promoted prototype records
   what was decided on its ADR's date and is never maintained against the
   shipped app (docs/agents/artifacts.md). So the drift catalogued above is the
   permanent state of these two files, not a backlog item.

   Touch targets on `.inv .chip`, `.inv .x`, `.inv .rm` and `.inv .keep` use
   `::after` padding -- ours alone, added with the dense one-line row. */

:root {
  color-scheme: light;
  --surface-1:  #fcfcfb;
  --plane:      #f9f9f7;
  --ink:        #0b0b0b;
  --ink-2:      #52514e;
  --ink-muted:  #898781;
  --grid:       #e1e0d9;
  --axis:       #c3c2b7;
  --hairline:   rgba(11,11,11,0.10);
  --series-1:   #2a78d6;
  --seq-450:    #2a78d6;
  --good:       #0ca30c;
  --warning:    #fab219;
  --serious:    #ec835a;
  --critical:   #d03b3b;
  --font: system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --surface-1: #1a1a19;
    --plane:     #0d0d0d;
    --ink:       #ffffff;
    --ink-2:     #c3c2b7;
    --ink-muted: #898781;
    --grid:      #2c2c2a;
    --axis:      #383835;
    --hairline:  rgba(255,255,255,0.10);
    --series-1:  #3987e5;
    --seq-450:   #3987e5;
  }
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
/* No overscroll-behavior here, deliberately. A pull-down at the top of
   .scroller only becomes a pull-to-refresh if it chains out of .scroller and
   reaches the viewport, which "participates in scroll chaining as the
   document's scrollingElement" (css-overscroll-1 section 3). .scroller is auto,
   so it chains; the viewport then performs its own boundary default action.
   Pull-to-refresh is a non-local boundary default action -- a navigation -- so
   both `contain` and `none` on the root suppress it, and MDN documents
   `html { overscroll-behavior: none }` as exactly the way to stop "Chrome on
   Android refreshes the page when you scroll past the top boundary". That is
   the bug: nothing here ever asked for it.
   Nothing needs a narrower version either. The document has no scrollable
   overflow on any screen (overflow is hidden, scrollHeight equals
   clientHeight), so chaining to it cannot translate the page -- there is no
   page scroll to rubber-band. What the UA may still do at that boundary is
   show a local affordance, which is the refresh spinner we want back.
   overflow: hidden stays, and it is doing different work from the tab bar's.
   height: 100% resolves through to the initial containing block, which tracks
   the *large* viewport, so on Android body stays about a toolbar taller than
   what is on screen even though #app is now 100svh and fits. Dropping overflow
   would hand the page that stray strip of scroll back, and that strip would
   rubber-band. */
html, body {
  height: 100%; overflow: hidden;
}
body { margin: 0; font-family: var(--font); background: var(--plane); color: var(--ink);
  -webkit-font-smoothing: antialiased; }
button { font: inherit; color: inherit; cursor: pointer; }
input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; }
h1, h2, h3, p, ul, ol, dl { margin: 0; }
ul, ol { padding: 0; list-style: none; }
.tnum { font-variant-numeric: tabular-nums; }

/* The shell commits to the small viewport. 100svh is the only unit the spec
   guarantees never exceeds what is actually on screen: it is measured with every
   retractable browser interface expanded, and it does not change while that
   interface animates. 100dvh is wrong here -- during the animation a UA may
   compute dv* as if the interface were fully retracted (css-values-4 recommends
   exactly that), i.e. the large viewport, so the shell is briefly taller than the
   screen and the tab bar lands in the strip #app clips. 100% is wrong the same
   way but permanently: it resolves through html/body to the initial containing
   block, which tracks the large viewport.
   One rung, not three: svh, lvh and dvh shipped together in every engine
   (Chrome 108, Safari 15.4, Firefox 101), so `height: 100svh; height: 100dvh`
   would be a declaration no engine can ever select. */
#app {
  width: min(430px, 100%); margin: 0 auto;
  height: 100svh;
  background: var(--surface-1); position: relative; overflow: hidden;
  display: flex; flex-direction: column;
  border-left: 1px solid var(--hairline); border-right: 1px solid var(--hairline);
}
/* Engines predating those releases have no small-viewport unit, and with no
   height at all the flex column grows past the document's clip and takes .nav
   with it. Give them the large viewport instead: the tab bar sits below the fold
   there, which is the old bug, but it is the best available and this rung cannot
   be selected by anything that understands svh. */
@supports not (height: 100svh) {
  #app { height: 100%; }
}
.scroller {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding-bottom: 20px;
  -webkit-overflow-scrolling: touch;
}
/* In-flow at the bottom of #app. position:fixed pins to the layout viewport,
   which on Samsung sits below the URL bar until you scroll. */
.nav {
  flex: none; z-index: 40;
  background: var(--surface-1); border-top: 1px solid var(--axis);
  padding-bottom: env(safe-area-inset-bottom);
}
.nav .seg { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; padding: 8px 10px 10px; }
.nav a, .nav button {
  border: 0; background: none; padding: 12px 4px; border-radius: 14px;
  font-size: 14px; font-weight: 650; color: var(--ink-muted);
  text-decoration: none; text-align: center; display: block;
}
.nav a[aria-current="page"], .nav button[aria-selected="true"] {
  background: var(--ink); color: var(--surface-1);
}
.nav .badge { display: inline-block; min-width: 18px; margin-left: 5px; padding: 1px 5px;
  border-radius: 9px; background: var(--series-1); color: #fff; font-size: 11px; }
.nav a[aria-current="page"] .badge, .nav button[aria-selected="true"] .badge {
  background: var(--surface-1); color: var(--ink);
}

.chip { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; font-weight: 600;
  white-space: nowrap; color: var(--ink-2); }
button.chip { border: 0; background: none; padding: 0; }
.chip .dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.chip.s-good .dot     { background: var(--good); }
.chip.s-warning .dot  { background: var(--warning); }
.chip.s-serious .dot  { background: var(--serious); }
.chip.s-critical .dot { background: var(--critical); }
.meter { height: 6px; border-radius: 3px; background: var(--grid); overflow: hidden; }
.meter > i { display: block; height: 100%; border-radius: 3px; background: var(--seq-450); }
.meter.low > i      { background: var(--warning); }
.meter.critical > i { background: var(--critical); }

.top { padding: 18px 18px 0; display: flex; align-items: flex-start; gap: 10px; }
.top > div { flex: 1; }
.top .date { font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-muted); font-weight: 700; }
.top h2 { font-size: 26px; letter-spacing: -0.02em; margin-top: 4px; }
.body { padding: 16px 18px 0; }
.lede { font-size: 15px; color: var(--ink-2); line-height: 1.5; margin-bottom: 16px; }
.hero { background: var(--plane); border: 1px solid var(--hairline); border-radius: 20px; padding: 20px; }
.hero .when { font-size: 12px; font-weight: 700; color: var(--series-1);
  letter-spacing: .06em; text-transform: uppercase; }
.hero h1 { font-size: 30px; line-height: 1.1; margin: 8px 0 12px; letter-spacing: -0.02em; }
.hero .meta { display: flex; flex-wrap: wrap; gap: 6px 14px; color: var(--ink-2); font-size: 14px; }
.why { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--grid);
  font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.sec { font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-muted); font-weight: 700; margin: 26px 0 8px; }
.card { border: 1px solid var(--hairline); border-radius: 18px; overflow: hidden; }
.card > * + * { border-top: 1px solid var(--grid); }
.ing { display: flex; align-items: center; gap: 12px; padding: 14px 16px; }
.ing .nm { flex: 1; font-size: 16px; font-weight: 550; }
.ing .qt { color: var(--ink-2); font-size: 14px; }
.ing.on .nm { color: var(--ink-muted); text-decoration: line-through; }
.ing .tag { font-size: 11px; font-weight: 700; color: var(--critical); text-transform: uppercase;
  letter-spacing: .04em; }
.x { border: 0; background: none; color: var(--ink-muted); font-size: 18px; padding: 6px; }
.box { width: 26px; height: 26px; border-radius: 9px; border: 2px solid var(--axis); flex: none;
  display: grid; place-items: center; font-size: 14px; color: transparent; background: none; padding: 0; }
.box.on { background: var(--series-1); border-color: var(--series-1); color: #fff; }
.cta { display: block; width: 100%; margin-top: 14px; padding: 17px; border-radius: 16px;
  border: 0; background: var(--series-1); color: #fff; font-size: 16px; font-weight: 650;
  text-align: center; text-decoration: none; }
.cta.ghost { background: var(--plane); color: var(--series-1); border: 1px solid var(--hairline); }
.steps { display: grid; gap: 10px; }
.steps li { display: grid; grid-template-columns: 20px 1fr; gap: 10px;
  font-size: 14px; line-height: 1.5; color: var(--ink-2); }
.steps li b { color: var(--series-1); font-variant-numeric: tabular-nums; }
/* One item, one row: name, state, location. Stock is the view that strains the
   "fits one screen" budget first, and stacking the state under the name cost
   90px a row -- sixteen items scrolled. At 43px the same sixteen fit without
   scrolling, which is the whole point of the screen: see what the house is out
   of without moving your thumb.
   The name is the only elastic thing on the row. min-width:0 is what lets it
   shrink below its own text -- a flex item floors at min-content otherwise, so
   without it a long name pushes the location pill off the row instead of
   truncating itself. Everything to its right is flex:none and holds its place,
   which is also why the width the expiry gave back on 2026-08-19 went to the
   name and nowhere else. */
.inv { display: flex; align-items: center; gap: 8px; padding: 10px 12px 10px 14px; }
.inv .nm { flex: 1; min-width: 0; font-size: 15px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inv form { flex: none; display: flex; }
.inv .chip, .inv .loc { flex: none; }
.inv .loc { border: 1px solid var(--axis); border-radius: 999px; padding: 2px 8px;
  font-size: 11px; font-weight: 650; color: var(--ink-2); }
.inv .x { padding: 2px 0 2px 2px; font-size: 16px; }
.inv .add { border: 0; background: none; color: var(--series-1); font-weight: 700; font-size: 13px; padding: 2px 4px; flex: none; }

/* The two controls on a row are small print -- a 12px chip and an 11px glyph --
   in a row that is 43px tall and has to stay that way, because sixteen items only
   fit one screen at that height. iOS asks for 44px of touch target and Android
   for 48dp, and there is no height to give either of them.
   So the hit area is separated from the drawn box: each control gets an
   absolutely positioned ::after, centred on it and at least 44px square. It is
   out of flow, so it costs the row no height; it has no paint, so it costs the
   row no ink; and because it is a child of the button, a thumb landing on it
   presses the button. This is the only absolute positioning in the file and the
   only pseudo-element -- the alternative, padding plus a matching negative
   margin, does the same arithmetic in two places instead of one and breaks the
   moment someone edits the padding.
   44px of height is the one part of this that a 43px row cannot actually give:
   the overlay is declared 44px and overhangs both neighbours, but the row below
   paints over the overhang, so what a control really owns is its own row band.
   Measured in chromium at 390x844 and 430x932 that is 44.9 x 42.0px for the state
   chip and 45.0 x 42.0px for the delete, up from 29.2 x 12.9 and 12.3 x 22.9. The
   missing 2px of height is what buys sixteen items on one screen, and it costs
   nothing: the column is continuously tappable, so a tap that lands 2px high hits
   the row above's chip rather than, as before, thirty pixels of nothing. */
.inv .chip, .inv .x, .inv .rm, .inv .keep { position: relative; }
.inv .chip::after, .inv .x::after, .inv .rm::after, .inv .keep::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%); width: 100%; min-width: 44px; height: 44px; }
/* The two controls at the row's right edge sit flush against its 12px of padding,
   so an overlay centred on them would spill past the card -- which clips it away,
   since overflow: hidden is what draws the rounded corners and the row rules.
   Anchor those two to the outer edge instead. They gain the padding a thumb
   already reads as part of the control, and keep their full width inside the
   card. */
.inv .x::after, .inv .keep::after { left: auto; right: -12px; transform: translateY(-50%); }

/* A row asking to be removed. Delete has no undo and item names are not unique,
   so putting the name back is not the same item coming back -- the two taps are
   the whole protection, and the question has to be readable in the 22px of row
   the one-line budget leaves. Hence text buttons at chip size rather than
   anything with a box around a glyph, and hence the tone on the border and the
   ink rather than a filled button, which at this size reads as a colour and not
   as a word. */
.inv .rm, .inv .keep { border-radius: 999px; padding: 4px 12px; background: none;
  font-size: 11px; font-weight: 650; }
.inv .rm { border: 1px solid var(--critical); color: var(--critical); }
.inv .keep { border: 1px solid var(--axis); color: var(--ink-2); }
.stepper { display: flex; align-items: center; gap: 4px; }
.stepper button { width: 38px; height: 38px; border-radius: 12px; border: 1px solid var(--axis);
  background: var(--plane); font-size: 19px; line-height: 1; }
.stepper .val { min-width: 60px; text-align: center; font-size: 15px; font-weight: 650; }
.addbar { display: grid; grid-template-columns: 1fr; gap: 8px; }
.addbar input { padding: 15px 16px; border-radius: 14px; border: 1px solid var(--axis);
  background: var(--plane); font-size: 16px; min-width: 0; }
.addbar button, .addbar input[type="submit"] { grid-column: 1 / -1; padding: 15px; border-radius: 14px; border: 0;
  background: var(--ink); color: var(--surface-1); font-weight: 650; font-size: 15px; }
.shop { display: flex; align-items: center; gap: 12px; padding: 14px 16px; }
.shop .nm { font-size: 16px; font-weight: 550; }
.shop .sub { font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--series-1); margin-top: 2px; }
.shop.done .nm { text-decoration: line-through; color: var(--ink-muted); }
.shop.done .sub { color: var(--ink-muted); }
.meal-add { display: grid; gap: 8px; }
.meal-add input, .meal-add textarea {
  padding: 15px 16px; border-radius: 14px; border: 1px solid var(--axis);
  background: var(--plane); font-size: 16px; min-width: 0;
}
.meal-add textarea { resize: vertical; }
.meal-add input[type="submit"], .meal-add button[type="submit"] { padding: 15px; border-radius: 14px; border: 0;
  background: var(--ink); color: var(--surface-1); font-weight: 650; font-size: 15px; }
.inline { display: inline; }
