/* ═══ mobile.css — the phone, built from the frozen mockup's 18 frames
   (design/vantage.html screen 9) and the audit's §6 mobile rules.

   THE RULE THIS FILE OBEYS: unified = zero silent differences. Desktop wins
   by default — every phone screen is the SAME markup as the desktop screen,
   restyled. Nothing here rebuilds a component for touch; a deliberate
   desktop≠phone difference has a row in design/README's divergence ledger,
   and a split without a row is a bug.

   Loaded AFTER vantage.css so it wins on equal specificity, and everything
   lives inside one breakpoint so the desktop cascade is untouched.

   THE BREAKPOINT IS 900px. The design has exactly two layouts and no third:
   the dashboard needs 1200 and the sheets need 1000, so anything under 900
   is the phone. That puts a portrait tablet on the phone design deliberately
   — a squeezed desktop is worse than an honest phone.

   The four laws every rule below serves (audit §6):
     · a tap REPLACES the screen — no sheet-over-home theatre at 390px
     · every hover reveal is visible at rest, dimmed — phones cannot hover
     · touch targets ≥44px; nothing a user must read under 15px
     · the dock is the nav, and home is the way back                     ═══ */

/* ---------- THE DOCK (verdict 108/112) ----------------------------------
   Above the breakpoint it does not exist; the desktop has its top-right
   cluster. Declared outside the media query so the hidden state is the
   default and no desktop paint ever flashes it. */
.mdock{display:none}
/* A PHONE-ONLY ELEMENT needs a ledger row in design/README — there are two,
   and each is there because the desktop states the same fact somewhere the
   phone has no room for: the automate home card (row 5, the desktop's cluster)
   and the settings import row (row 4, same). Hidden by default so the desktop
   never paints them. */
.m-only,.prow-mobile{display:none}

@media (max-width:900px){

/* ---------- shell -------------------------------------------------------
   The gutters match the app's card rhythm (owner 18-Jul: "mobile breathes
   like desktop"). The bottom padding is THE DOCK INSET: the dock floats out
   of flow, so every screen owes it its footprint — 58px pill + 18px float +
   a 14px gap. Content may scroll UNDER the dock; it may never END under it. */
/* the top padding owes the STATUS BAR its footprint: viewport-fit=cover lets
   the page under the clock, and in the installed (home-screen) app the page
   actually goes there — the owner's on-device pass caught the settings
   heading and the import ✕ sitting beneath the time (27-Jul-26). env() is 0
   in a normal browser tab, so this is a no-op everywhere it isn't needed. */
main{padding:calc(18px + env(safe-area-inset-top,0px)) 16px
             calc(90px + env(safe-area-inset-bottom,0px))}
.wrap{max-width:none}
.page-head{padding-right:0;margin-bottom:16px;flex-wrap:wrap}
/* the merchant sheet's ✕-clearance is desktop-only — the ✕ is display:none
   here (the dock is the way back), so the padding would be dead space */
.um-head{padding-right:0}
/* the cluster's four doors are re-homed: search + settings are dock seats,
   import is a settings row (ledger row 4), automate is a home card (row 5) */
.controls{display:none}
/* …EXCEPT THE REPORT EYE (owner, 25-Jul-26), which has nowhere else to go: the
   dock's three seats are spoken for and its middle one is already the commit
   eye, so a second eye down there would be two marks meaning two things. It
   stays where the desktop puts it — top-right, above the page title — as the
   cluster's only surviving seat. Nothing is duplicated: this is the same
   button, and on the phone it is the only one the row still shows. */
.controls:has(.creport){display:flex;justify-content:flex-end}
.controls:has(.creport)>:not(.creport){display:none}
/* the ≥44px touch floor (audit §3-6). Its four neighbours are hidden here, so
   the cluster's 34px box never had to meet it; this seat does, being the only
   one the phone shows. Measured at 34 before this. */
.controls .creport{width:44px;height:44px;margin:-5px -5px 0 0}
h1{font-size:15px}
.m-only{display:block}
.mtrunc{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;min-width:0}

/* one scroller, and it is the window. The desktop's sheets and card bodies
   each own a scroll region; nesting those inside a scrolling phone page is
   how a touch drag ends up moving the wrong thing. */
body.has-overlay,body:has(.vsheet){overflow:visible}

/* ---------- home: the cards stack, in the phone's own order -------------
   The desktop's two grids pair cards side by side; stacked, those pairings
   stop meaning anything and only the SEQUENCE does. `display:contents`
   dissolves both grids so every card becomes a direct child of one column,
   and `order` puts them in the frames' sequence: stance · automate · tracks ·
   categories · recurring · merchants · transactions.
   The automate card is the reason this is needed — it belongs between stance
   and tracks (ledger row 5), and it lives outside both grids. */
.hbase{display:flex;flex-direction:column;gap:12px}
.topgrid,.grid2{display:contents}
.hbase>.page-head{order:0}
.hbase .hero{order:1}
.hbase>.m-only{order:2}
.hbase .flow{order:3}
.hbase .cats-card{order:4}
.hbase .grid2>a:not(.cats-card){order:5}
.hbase .merch{order:6}
.hbase>.card:last-child{order:7}
.topgrid>.card,.grid2>.card,.hbase>.card{margin:0!important}
.grid2>.card{height:auto}
/* the fade over a scrolling card body is the desktop card's own cue and it
   still tells the truth here — the body still scrolls, just shorter */
/* the card body keeps the desktop component's own scroll-in-card behaviour,
   at a phone-appropriate height — `overscroll-behavior:contain` (vantage.css)
   is what stops a nested drag from chaining into the page */
.grid2 .w-body{max-height:46vh}
.tscroll{max-height:46vh}
.hero{padding:18px 18px 16px}
.hero .big{font-size:28px}
.hero .door{top:15px;right:16px}
.w-body{padding:12px 16px 14px}
.card-h{padding:12px 16px}
/* A REAL door link needs a real finger. Most cards ARE the door — the whole
   `<a class="card">` is the target and its `›` is decorative (`aria-hidden`,
   a span). Two cannot be: biggest-merchants and the transactions preview have
   LINKS INSIDE THEIR ROWS (a merchant name, a transaction), and an anchor may
   not contain another, so on those two the `›` itself is the only door. It
   measured 12x14 (28-Jul-26) against the 44px touch floor every other control
   on this layer clears — three cards opening from anywhere and two demanding a
   12px glyph is also two different rules for one gesture.
   Padding grows the target, the negative margin keeps the arrow drawn exactly
   where it was, so nothing moves and the header does not grow. */
a.door{display:inline-flex;align-items:center;justify-content:center;
       width:44px;height:44px;margin:-15px -14px}

/* the pulse rows at phone width: the forms keep every bit of their meaning
   at 34px (the miniature law, verdict 100) — only the box shrinks. Ledger
   row 18: nothing is dropped. */
.pulse-home{padding:0 4px 4px}
.pulse-home .plrow{gap:9px;padding:13px 8px}
.pulse-home .plnm{width:48px}
.pulse-home .pl-pair,.pulse-home .pl-track,.pulse-home .pl-week,.pulse-home .pl-mo{width:34px}
.pulse-home .pl-spark{width:34px;height:14px}
/* the value and its comparison STACK, right-aligned (ledger row 25). On the
   desktop they are two columns of a 560px row; at 356px they cannot both fit
   on one line, and dropping the comparison would lose the only figure that
   says which way the fact is moving. Same reasoning as ledger row 8's stat
   boxes: the information survives, the arrangement changes. */
.pulse-home .plrow{flex-wrap:wrap;row-gap:2px}
.pulse-home .plval{font-size:12px;margin-left:auto;text-align:right;flex:0 1 auto;min-width:0}
/* ONE LINE PER PULSE ROW (owner, 27-Jul-26, overruling ledger row 25's stack:
   "keep it single line, decide which piece is more important"). The VALUE is
   the row's answer; the comparison is its annotation, and the tracks screen —
   one tap away — still carries it. So the phone shows the value and drops the
   detail, instead of stacking two lines. */
.pulse-home .pldl{display:none}
/* the categories card's " of $X" half is already container-queried away
   below 520px of card — at 356px it is always the squished form.
   The ellipsis lives on `.nmt` (the name), never on `.nm` (the whole cell), or
   it eats the ↻ roll-over badge — measured 28-Jul-26: 128px of box, 133px of
   "subscriptions ↻", and the badge is what disappeared. A name may be cut; a
   mark that carries meaning may not. */
.env .nm{width:96px}

/* ---------- sheets are FULLSCREEN (ledger row 2) ------------------------
   Owner, 19-Jul: "don't imitate the desktop popup — we don't have the space,
   and it looks stupid." A tapped card REPLACES the screen, so the frosted
   home behind it is not dimmed — it is GONE, and the dock's home icon is the
   way back (ledger row 22). */
.ovl-veil{display:none}
.wrap:has(.vsheet) .hbase{display:none}
.vsheet,.vsheet.vs-import{position:static;top:auto;left:auto;transform:none;
  width:auto;max-height:none;background:none;border:0;border-radius:0;
  box-shadow:none;padding:0;overflow:visible;display:block}
.sbody{flex:none;min-height:0;overflow:visible;margin:0;padding:0}
/* NO ✕ on a phone screen — home is the way back. The survivors are the two
   screens with a DISCARD outcome that differs from "go home": import (parsed
   files) and categories-reorder (a new order), which keeps its own ✕ inside
   the sheet's reobar. Two outcomes need two controls. */
.ovl-x{display:none}
.ovl-x.mkeep{display:grid;position:static;width:44px;height:44px;margin:-6px -10px 0 auto}

/* ---------- the row anatomy: date · name · amount · action --------------
   The phone's transaction row is exactly what the frozen frames draw: the
   desktop row with its two quietest columns dropped. Category and account are
   both a tap away (the merchant view states them), and at 356px they cost the
   name the width it needs to be readable at all. Ledger row 24. */
.hcat,.hcad{display:none}
/* the merchant "by month" swaps its axis chart for bar rows (ledger row 17) */
.bym-desk{display:none}
.bym-phone{display:block;padding-bottom:8px}
th{padding:8px 6px}
td{padding:11px 6px}
/* ONE LINE DOES THE WHOLE JOB: `overflow-wrap:anywhere` on the name cell.

   The problem it solves, measured twice: a real bank description ("GOOGLE
   *YOUTUBEPREMI G.CO/HELPPAY# CA") contains unbreakable runs wider than a
   390px screen, so the table's MINIMUM width exceeded the frame and the amount
   was pushed clean off the right edge. Letting the name break mid-run drops
   its minimum contribution to one character, which lets the table fit — and
   the two figures, being `nowrap`, keep their exact widths while the name
   takes whatever is left.

   TWO THINGS THAT DO NOT WORK, both tried and measured:
   · `table-layout:fixed` — a `display:none` cell still forms a COLUMN, so the
     two dropped columns silently took ~55px each of the name's share and the
     name ended up narrower than the amount.
   · `display:flex` on the `td` — a flexed cell stops being a table cell (the
     table wraps it in an anonymous one) and the columns stop lining up
     between rows.

   THE NAME WRAPS RATHER THAN TRUNCATING (ledger row 26). The frozen frames
   draw a truncating name and truncation is the better LOOK — but the frames'
   merchants are short invented ones, and a merchant name is the one thing on
   the row you cannot infer from the rest of it. So a long name costs a second
   line instead of its own meaning. Flagged for the on-device pass: this is
   exactly the kind of call that reads differently in the hand. */
td.nm,td.catname{overflow-wrap:anywhere}
/* THE WAITLIST ROW STACKS (owner's screen). Three columns — address, code,
   two buttons — do not fit 375px: the actions took half the width and
   `overflow-wrap:anywhere` above (right for a bank description, wrong for an
   email in a narrow cell) broke every address down a character per line.
   Stacked, each row reads address → code → the decision, in that order.
   Ledger row: this is a layout change, not a different set of controls, so it
   is the desktop design at a width it fits. */
.wl-row{display:block;padding:12px 0}
.wl-row>td{display:block;border:0;padding:2px 12px}
.wl-row>td.nm{overflow-wrap:normal;word-break:break-all}
.wl-row .wl-code{padding:4px 12px}
/* the ≥44px touch floor: these are 20px-tall text buttons on desktop, where a
   cursor is precise; on a phone they are the only two controls on the row and
   one of them removes somebody */
.wl-row .wl-acts{justify-content:flex-start;padding-top:8px;gap:14px}
.wl-row .wl-acts button{min-height:44px;padding:0 14px}
.wl-row+.wl-row{border-top:1px solid var(--line)}
.wl-counts{width:100%}
.date{white-space:nowrap;font-size:11px}
td.r,th.r{white-space:nowrap}
/* the action column: 44px is the touch floor, whether it holds a ⋯, a ↩ or
   nothing at all */
tr>td:last-child:not(.nm):not(.cat):not(.r),tr>th:last-child:not(.r){width:44px}
td.nm .tag,td.nm .stag{margin-left:8px}

/* ---------- every reveal visible at rest, dimmed (audit §3-6) -----------
   Phones do not hover, so a hover-only control is an invisible control.
   The set: the row ⋯, the restore ↩, a proposal line's ✓, the goal ⋯, the
   pair-restore. They sit at .55 opacity — present, not shouting — and each
   grows to the 44px touch floor. */
.dots,.undo,.li-yes,.pair .btn-g,.iq-never{opacity:.55}
.dots{width:44px;height:44px;font-size:16px;margin:-10px -10px -10px 0}
.undo{width:44px;height:44px}
.li-yes{width:38px;height:34px;opacity:.55}
.iq .dots,.rc-row .dots,.goal .dots{opacity:.55}
/* the category cell's caret is part of the picker, not a hover hint */
.catw .car{opacity:1}
.addann{opacity:.8;padding:12px 11px;margin:-12px -4px}

/* ---------- touch floors (audit §6) ------------------------------------- */
.btn{padding:13px 16px;min-height:48px}
.btn-enter{width:auto;min-width:72px;height:48px}
.cbtn,.mv-rename,.tmore,.pmark{min-width:34px;min-height:34px}
.pmark{width:34px;height:26px}
.msel .mstep{width:34px;height:34px;font-size:16px}
.addbtn,.setpk{min-height:44px;padding:10px 14px}
.tfoot .link{padding:8px 6px;margin:-8px -6px}
/* AN INLINE TEXT CONTROL GETS A TALL HIT AREA WITHOUT GETTING TALLER.
   Measured: the sort headers were 17px high and the row names 20–22px — all
   under the touch floor, all of them the primary way into their screen. Padding
   plus an equal negative margin grows the tappable box while the text stays
   exactly where the design put it, so nothing reflows. */
th button.sortb{padding:13px 4px;margin:-13px -4px}
.wdoor,.pockets .cn{padding:11px 7px;margin:-11px -7px}
.prow .btn-g,.legal-links a,.au-links a{padding:12px 8px;margin:-12px -8px}
.mv-rename{padding:9px 10px;margin:-9px -4px}
.snack-act{padding:12px 10px}
/* iOS ZOOMS THE PAGE when a focused field's text is under 16px, so every
   field with room for it gets 16px — the search bar, the account and category
   names, the auth fields, the feedback box. The narrow numeric cells (a
   pocket, an amount) stay at the chrome's own size: 16px mono digits do not
   fit an 84px box, and a brief zoom on a field you are typing a number into is
   the smaller cost of the two. */
input,select,textarea{font-size:16px}
/* the search field FILLS its prompt: the box was 25px tall inside a 55px
   field, so two-thirds of the thing you obviously tap did nothing. The padding
   moves from the prompt onto the input — same height on screen, the whole of it
   tappable. */
.cprompt{padding:6px 18px}
.cprompt input{font-size:16px;padding:13px 0}
.facct{padding:10px 11px;font-size:16px}
.prow{min-height:56px;padding:0 16px}

/* ---------- the month stepper gets its own line (ledger row 12) ---------
   The sheet header row is too tight for two 34px steps beside a title. */
.page-head .msel,.card-h .msel{width:100%;margin:8px 0 0;height:44px;justify-content:flex-start}
.page-head .msel .mval,.card-h .msel .mval{min-width:110px}

/* ---------- stat headers become one stacked line (ledger row 8) ---------
   Three .mv-stat boxes measure ~110px each at 390px — unreadable. They
   become one column, which is the same information in the same language. */
.mv-stats{flex-direction:column;gap:8px;margin-bottom:14px}
.mv-stat{padding:11px 14px;flex-direction:row;align-items:baseline;gap:10px;flex-wrap:wrap}
.mv-n{font-size:18px}
.tr-card .mv-stats{margin:12px 16px 0}

/* ---------- charts: tap labels, never hover (audit §6) ------------------
   The SVGs are viewBox-scaled, so they fit by themselves — what does not fit
   is an 11px axis label inside a drawing squeezed to 356px, and a <title>
   tooltip that only a mouse can summon. So: the axis keeps its labels (they
   scale with the viewBox and stay legible because the viewBox is fixed), and
   every dot/day carries a real tap target rather than a hover title.
   Ledger row 16 — the phone's chart controls are live here, not static. */
.tr-body{padding:12px 16px 4px}
.tr-hero{padding:13px 16px 0;gap:8px}
.tr-num{font-size:20px}
.ins{padding:4px 16px 11px}
.rangec{font-size:12px}
.rangec a,.rangec button{padding:10px 12px}
/* habits: the year grid's 11px cell is far under the touch floor, and no cell
   size can fix that — a year is 365 of them. The MONTH view is the answer, and
   it is what a phone opens on (verdict 96): seven columns across 356px gives
   ~44px cells, which clears the floor exactly. The year grid stays reachable
   and stays a picture rather than a set of controls — bigger cells, so it
   shows fewer weeks (ledger row 15). */
.hm-grid{gap:4px}
.hm-d{width:14px;height:14px}
/* 44px exactly: seven cells and six 4px gaps across the card's inner width,
   with the grid reaching 4px into the body's padding on each side. Measured —
   at gap 5 inside the padding the cells came out 42px. */
.hm-cal{max-width:none;gap:4px;margin:0 -4px}
.hm-cal .hm-d{font-size:13px}
/* drift's row: the name column gives up width to the bars, and the meta
   line drops under them rather than off the edge */
.dr-row{flex-wrap:wrap;gap:8px 10px}
.dr-nm{width:92px}
.dr-meta{width:auto;flex:1;text-align:right}
/* a STANDALONE caption has to wrap. `.fmeta` is nowrap because it usually sits
   inline in a row where wrapping would break the row's alignment — but as its
   own paragraph that same rule pushed the habits card 84px past the screen
   (measured). A block-level caption is a different job from an inline one. */
p.fmeta{white-space:normal}

/* ---------- import ------------------------------------------------------ */
.drop{padding:22px;min-height:112px}
.drop .eye{width:38px;height:30px}
.iq{gap:10px;padding:10px 14px;flex-wrap:wrap}
.iq>.stag{width:auto;min-width:52px}
.iq-rows{flex:1 1 100%;order:2}
.iq-act{order:3;margin-left:auto}
.iq-yes,.iq-no{width:44px;height:44px}
.iq-undo{height:44px}
.st-confirm .facct{flex:1 1 auto;min-width:0;width:auto}
.st-parsing .iq-li>.nm,.st-confirm .iq-li>.nm{max-width:100%}
.ip-adj-grid{gap:10px}
.rprog{width:64px}

/* ---------- recurring: the add row has to fit the frame ------------------
   Five fields in one table row is a desktop shape. A `date` input carries a
   wide intrinsic minimum (its own calendar chrome) and a `select` carries its
   longest option, so left alone the add row alone pushed the table to 637px on
   a 390px screen — measured. Every field gives up its intrinsic width here and
   takes a share of the row instead. */
.rc-add input,.rc-add select{width:100%;min-width:0;font-size:13px;padding:9px 6px}
.rc-add input[type=date]{font-size:12px}
.rc-row .cat,.rc-add td{padding:10px 4px}
/* AND THE SAFETY NET, for this table and any other that genuinely can't fit:
   a wide table scrolls INSIDE its own card rather than dragging the page
   sideways with it. A `date` input's calendar chrome has a ~123px intrinsic
   minimum that no font size gets under, so five columns of recurring cannot be
   made to fit 356px — and the alternatives were both worse: clip a column (and
   lose the ability to set a cadence at all) or wrap the cells (and break the
   column alignment every row depends on). A table that fits never scrolls, so
   this costs the other screens nothing. */
.card:has(>table){overflow-x:auto}

/* ---------- automate: one answer per tap, 44px each -------------------- */
.iq.here{box-shadow:inset 3px 0 0 var(--acc)}

/* ---------- categories: name · bar · pocket · ⋯ ------------------------
   The mockup's phone row. The "spent of available" sentence steps aside —
   the bar already draws it, and the pocket input is the figure that can be
   edited, so it keeps the width. */
.pockets .use{width:auto}
.pockets .use .amt{display:none}
.pockets .use .bar{width:100%;min-width:40px;margin-right:0}
/* the pocket figure keeps the chrome's own size: a five-figure pocket
   ("12,345.00") has to fit, and 16px mono digits do not */
.pockets input.pk,.pockets .setpk{width:90px;font-size:13px;padding:9px 7px}
.pockets .catname{max-width:104px}
.pockets .drag{font-size:18px;padding:0 8px}
.reordering .pockets .drag{padding:10px 8px}
.reobar button{width:44px;height:44px}
/* the ✓ IS THE DOCK'S EYE here (verdict 112), so the in-bar one would be the
   same commit twice. The ✕ stays: discarding the order is a different outcome
   from keeping it, and two outcomes need two controls (ledger row 22). */
.reobar .ok{display:none}

/* ---------- settings: the phone's third door -------------------------- */
.prow .pval{font-size:12px}
.help-panel dl{grid-template-columns:1fr;gap:4px 0}
.help-panel dt{text-align:left}
.help-panel dd{padding-bottom:6px}
.legal-links{font-size:12px}
/* import has no dock seat (ledger row 4) — it lives here */
.prow-mobile{display:flex}

/* ---------- auth + first run ------------------------------------------ */
.frs-wrap,.su-wrap{min-height:calc(100vh - 190px)}
/* the lockup keeps its hero size on the phone — one design, one element
   (owner, 27-Jul-26); the old 72px eye override died with the old eye */
.frs-keys{display:none}                  /* no keys to teach (ledger row 11) */
.su-cur{gap:6px}
.su-cbtn{min-width:86px;min-height:48px}
.su-name{width:100%}
.hm-empty{min-height:calc(100vh - 150px)}

/* ---------- the why sheet + the ? card at phone width ------------------ */
.whysheet .wp{width:calc(100vw - 24px);max-height:88vh;padding:52px 18px 40px}
.whysheet .wp-x{width:44px;height:44px;top:8px;right:8px}
.whysheet .wp-why{font-size:15px}
.whysheet .wp-ev{width:100%}
.scut{display:none!important}            /* a phone has no keyboard to card */

/* ---------- the row context menu as a bottom sheet ---------------------
   The ONE survivor of the sheet language (ledger row 3): a long-press menu
   is a menu, not a destination, so it hugs the bottom the way every phone OS
   does it — full width, above the dock, with a drag handle. */
.ctx{left:0!important;right:0!important;bottom:0!important;top:auto!important;
     width:100%;min-width:0;max-height:72vh;overflow-y:auto;
     border-radius:20px 20px 0 0;border-bottom:0;
     padding:8px 14px calc(18px + env(safe-area-inset-bottom,0px))}
.ctx::before{content:"";display:block;width:36px;height:4px;border-radius:2px;
             background:var(--line2);margin:2px auto 12px}
.ctx button{min-height:48px;font-size:15px;padding:10px 12px}
.ctx .ctx-in{min-height:48px;font-size:15px}
.ctx .ctx-hint{max-width:none;font-size:13px}
.ctx .ctx-list{max-height:46vh}

/* ---------- the snackbar clears the dock (ledger row 9) ----------------
   The dock owns the bottom edge here: 58px pill + 18px float + a 10px gap. */
.snack{left:12px;right:12px;bottom:calc(86px + env(safe-area-inset-bottom,0px));
       transform:none;white-space:normal;
       flex-wrap:wrap;gap:10px}
.snack .prose{flex:1 1 100%}
.snack-act{margin-left:auto}

/* ---------- the failure snackbar ---------------------------------------
   It clears THE DOCK, exactly as `.snack` above does (86px, ledger row 9):
   desktop owns the bottom edge, here the dock does, and a failure underneath
   the dock is a failure with an unreachable ✕. Gutter to gutter, because a
   centred pill at phone width has nothing to centre in. */
.snackstack{left:12px;right:12px;bottom:calc(86px + env(safe-area-inset-bottom,0px));
            transform:none;max-width:none}

/* ═══ THE DOCK ═══════════════════════════════════════════════════════════
   A floating three-icon pill — home · ⌕ search · settings — over every
   screen. Icons only, ≥44px seats, active seat azure in the soft wash.
   It grows in WIDTH only (owner, 23-Jul), never in height, so the eye is
   free to stand proud of the pill. */
/* the float clears the HOME INDICATOR too (owner, 27-Jul-26, on-device: "too
   close to the edge — refer to apple guidelines"): HIG keeps floating controls
   out of the indicator's safe area, so the 18px float sits ON TOP of
   env(safe-area-inset-bottom) — 52px from the glass in the installed app,
   unchanged 18px in a browser tab where the inset is 0. */
.mdock{display:flex;position:fixed;left:50%;transform:translateX(-50%);
       bottom:calc(18px + env(safe-area-inset-bottom,0px));
       gap:6px;padding:6px;border-radius:99px;overflow:visible;
       background:color-mix(in srgb,var(--card2) 90%,transparent);
       border:1px solid var(--line2);box-shadow:0 14px 34px rgba(0,0,0,.5);
       backdrop-filter:blur(12px);z-index:var(--z-dock);
       transition:transform .26s cubic-bezier(.2,.8,.2,1),opacity var(--t-med)}
.mdock .mdi{width:54px;height:44px;display:grid;place-items:center;
            color:var(--tx3);border-radius:99px}
.mdock .mdi svg{width:20px;height:20px}
.mdock .mdi.on{color:var(--acc2);background:var(--accsoft)}
/* HIDE ON SCROLL (verdict 110, owner's yes): the nav steps out of the way
   going down and returns coming up — a floating bar that never leaves is a
   floating bar always in the way. Scoped to the NAV: while a commit is armed
   nothing hides, because hiding the dock would hide the commit, and a control
   you have to go hunting for is not a control. The inset stays either way. */
.mdock.hid{transform:translateX(-50%) translateY(calc(96px + env(safe-area-inset-bottom,0px)));
           opacity:0;pointer-events:none}

/* THE COMMIT IS THE EYE (verdict 112). The dock keeps its pill, its height
   and its three seats; only the SEARCH seat steps aside — the seat the owner
   ruled expendable ("its just taking away the search... the home is still
   there, thats all that matters"). Arming widens the dock and nudges home and
   settings outward, because a commit is primary and nav is secondary, and
   equal weight would hide that.
   WORDLESS BY RULE: the screen carries the fact ("59 transactions ·
   reconciled ✓"); the eye only says press me. It keeps an aria-label —
   wordless is not nameless. */
.mseat{--seat-w:64px;width:54px;height:44px;border-radius:99px;display:grid;
       place-items:center;position:relative;cursor:pointer;
       background:color-mix(in srgb,var(--brand) 14%,transparent)}
.mseat .meye{width:30px;height:24px;color:var(--brand);display:block;transform-origin:50% 58%}
.mseat .meye svg{width:100%;height:100%;display:block}
.mseat .mlid{position:absolute;left:50%;top:52%;transform:translate(-50%,-50%);
             width:30px;height:3px;border-radius:2px;background:var(--brand)}
.mseat:focus-visible{outline:1px solid var(--accline);outline-offset:3px}

/* THE W5 WAKE (owner, 23-Jul) — the beat ORDER is the design, and it matters
   more than any single value:
     1. the dock WIDENS AS THE LINE IS DRAWN — the line growing is what
        appears to push the pill open; width and line are one gesture.
     2. the line TENSES (a sideways stretch — the wind-up),
     3. then springs into the eye, overshooting and settling.
   820ms, and the slowness is the design — most of it is spent on beats 1
   and 2, which is where the character lives. ONE SHOT and `forwards`: the
   eye opens when the commit arms and STAYS open. There is no close.
   ⚠ Seat 64 / mark 30 is a PLACEHOLDER — the dock is deferred post-launch
   polish (owner, 23-Jul: the whole component, not the size). Nothing here is
   frozen; the wake and its beat order are the parts that survive on merit. */
.mseat.wake{width:var(--seat-w);animation:mseat-open .82s cubic-bezier(.2,.8,.2,1) forwards}
@keyframes mseat-open{0%{width:54px}37%,100%{width:var(--seat-w,64px)}}
.mseat.wake .mlid{animation:mlid-wake .82s ease-out forwards}
@keyframes mlid-wake{
  0%  {opacity:1;transform:translate(-50%,-50%) scaleX(.04)}
  37% {opacity:1;transform:translate(-50%,-50%) scaleX(1)}
  50% {opacity:1;transform:translate(-50%,-50%) scaleX(1.20)}
  58% {opacity:0;transform:translate(-50%,-50%) scaleX(.45)}
  100%{opacity:0;transform:translate(-50%,-50%) scaleX(.45)}}
.mseat.wake .meye{animation:meye-wake .82s ease-out forwards}
@keyframes meye-wake{
  0%,50%{opacity:0;transform:scale(1.12,.04)}
  59%   {opacity:1;transform:scale(.94,1.14)}
  70%   {opacity:1;transform:scale(1.04,.96)}
  80%   {opacity:1;transform:scale(.99,1.02)}
  88%,100%{opacity:1;transform:scale(1,1)}}
/* the armed dock nudges its nav seats outward — 188→198 (owner) */
.mdock:has(.mseat:not([hidden])){gap:11px}

}   /* ── end of the phone breakpoint ── */

/* the wake is motion, so it obeys the motion preference: the eye is simply
   open, with no beats at all */
@media (prefers-reduced-motion:reduce){
  .mseat.wake{animation:none;width:var(--seat-w)}
  .mseat.wake .mlid{animation:none;opacity:0}
  .mseat.wake .meye{animation:none;opacity:1;transform:none}
}
