/* ============================================================================
   mobile.css — THE PHONE SYSTEM LAYER (GO-MOBILE-DESIGN-001, 2026-09-16)

   ⛔ SCOPE: every rule in this file lives inside `@media (max-width: 480px)`.
   Desktop (and tablet) cannot move. The 11 baselines in
   `~/Desktop/dang-phone-modals/desktop-baseline/` are the fence, re-captured
   after this file lands.

   ⛔ WHY A NEW FILE AND NOT EDITS IN `app.css`. `app.css` is 1439 lines with
   exactly ONE mobile breakpoint (`:990`), which changes one grid to one column.
   The phone treatment was never written; it is being added, not corrected. A
   separate, last-loaded layer keeps the desktop rules byte-identical and makes
   the whole phone system reviewable in one place.

   ⛔ LOADED LAST in app.html / index.html / onboarding.html so its rules win
   without `!important` — except where the product sets an INLINE style, which
   is the only place `!important` appears (the due-day grid, §2).

   Evidence for every number here is in `~/Desktop/dang-phone-modals/`
   (MODAL_DESIGN_SPEC.md + measurements*.json).
   ========================================================================== */

@media (max-width: 480px) {

  /* ── §0 TOKENS FOR THE PHONE ─────────────────────────────────────────────
     One radius, one control height, one gutter, one hit floor. The hit floor
     already existed in `dang-tokens.css` as `--dang-hit-target: 44px` and had
     no consumers on any of the controls that failed it.                      */
  :root {
    --m-sheet-radius: 20px;      /* premium iOS sheet corner */
    --m-gutter: 20px;            /* one horizontal gutter for every sheet */
    --m-control-h: 52px;         /* CTAs and inputs share one height */
    --m-hit: var(--dang-hit-target, 44px);
    --m-safe-b: env(safe-area-inset-bottom, 0px);
    --m-safe-t: env(safe-area-inset-top, 0px);
  }

  /* ── §1 THE BOTTOM SHEET ─────────────────────────────────────────────────
     MEASURED BEFORE: `.app-overlay` centred a `max-width:480px`,
     `border-radius:22px`, `max-height:92vh` dialog with `padding:20px` — a
     desktop alert box on a phone, with zero safe-area handling. At 375×667 the
     Add-a-debt modal sat AT its 92vh cap (613.6px) with 112px of its form below
     the fold.

     `vh` is the LARGE viewport on iOS: 92vh can exceed what the user can see
     once browser chrome is showing, and it does not shrink when the keyboard
     opens. `dvh` is the honest unit here.

     AFFECTS 14 surfaces: log payment (+2 disclosures), the anonymous refusal
     gate, the conversion modal, add/edit debt, remove-debt confirm, debt
     detail, what-if, journey switcher/rename/delete/create, celebration,
     sign-in, anon-import, merge, pending-deletion.                            */
  .app-overlay {
    align-items: flex-end;
    padding: 0;
    /* iOS convention is a lighter scrim than a desktop modal's. */
    backdrop-filter: blur(1px);
  }

  .app-modal,
  .app-sheet {
    max-width: none;
    width: 100%;
    border-radius: var(--m-sheet-radius) var(--m-sheet-radius) 0 0;
    max-height: 88dvh;
    /* The grabber. Drawn, not an element, so no module's DOM changes. */
    padding-top: 20px;   /* 8px grabber inset + 5px grabber + 7px breathing */
    background-image: linear-gradient(var(--payoff-border, #DDD7CF), var(--payoff-border, #DDD7CF));
    background-repeat: no-repeat;
    background-position: 50% 8px;
    background-size: 36px 5px;
    background-origin: border-box;
  }
  /* The grabber sits in the 14px of new top padding; `.app-sheet` already had
     its own 28px top padding, so it only needs the rounding + width change. */
  .app-sheet { padding-top: 22px; background-position: 50% 8px; }

  /* Header · body · foot share ONE gutter. MEASURED BEFORE: head/body/foot used
     24px while `.app-conv` overrode to 18px (`app.css:1321-1322`), so the title
     and the close button jumped 6px between two modals a user sees seconds
     apart. */
  .app-modal__head { padding: 8px var(--m-gutter) 10px; }
  .app-modal__body { padding: 2px var(--m-gutter) 8px; }
  .app-modal__foot {
    padding: 12px var(--m-gutter) calc(14px + var(--m-safe-b));
    border-top: 0.5px solid var(--payoff-border-2, #E4E0DA);
    background: var(--payoff-card, #fff);
    gap: 6px;
  }
  /* ⛔ THE 18px EXCEPTION IS RETIRED ON PHONE. One family, one gutter. */
  .app-conv .app-modal__head,
  .app-conv .app-modal__foot,
  .app-conv .app-modal__body { padding-left: var(--m-gutter); padding-right: var(--m-gutter); }

  .app-modal__title { font-size: 20px; letter-spacing: -0.2px; }

  /* ── §2 THE DUE-DAY GRID — THE ONLY *BROKEN* DEFECT ──────────────────────
     MEASURED: the grid is INLINE-STYLED `grid-template-columns: 40px ×7` with
     `gap:5px` = 310px intrinsic, inside a body content box of 257px at 375×667
     and 275px at 393×852.
       · 393×852 → 35px overflow (visible bleed; `app.css:682` already admits it)
       · 375×667 → 53px overflow, and `.app-modal { overflow:hidden }` CLIPS
         days 7, 14, 21 and 28 — four of the most common statement dates, on the
         most common small iPhone.
     Fluid tracks make the grid fit any width, and 44px rows clear the hit floor
     in the same rule. `!important` is required and is confined to this block
     because the product writes these as inline styles.                        */
  /* ⛔ THE SELECTOR WAS SCOPED TO THE MODAL AND THE ONBOARDING ARC HAS THE SAME GRID.
     The in-arc debt form is the FIRST debt form a new user ever meets, and it received
     none of this. Keyed on the inline signature the product actually writes, so both
     instances are covered by one rule. */
  div[style*="grid-template-columns"][style*="40px"],
  .app-modal__body div[style*="grid-template-columns"] {
    grid-template-columns: repeat(7, minmax(0, 1fr)) !important;
    gap: 6px !important;
    width: 100% !important;
  }
  div[style*="grid-template-columns"][style*="40px"] > button,
  .app-modal__body div[style*="grid-template-columns"] > button {
    width: 100% !important;
    min-width: 0 !important;
    height: var(--m-hit) !important;
    font-size: 15px;
  }

  /* ── §3 THE 44px FLOOR ───────────────────────────────────────────────────
     MEASURED BEFORE: `.web-tab` 33.1px · `.app-modal__close` 59.4×29 ·
     day cells 40×36 · "No specific day" 275×23 · `.po-cta--sm` 39px.
     None reached 44. `--dang-hit-target` existed and was consumed by nothing. */
  .app-modal__close {
    min-width: var(--m-hit);
    min-height: var(--m-hit);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 10px;
    background: transparent;          /* a chip beside a title reads as an action */
    color: var(--payoff-text-3, #9E9A94);
    margin-right: -10px;              /* optical: the glyph, not its box, aligns */
  }
  .web-tab { min-height: var(--m-hit); display: inline-flex; align-items: center; justify-content: center; }
  .app-modal__body button { min-height: var(--m-hit); }
  .po-cta--sm { min-height: var(--m-hit); }
  .app-strat__btn { min-height: var(--m-hit); }
  .app-debt__log { min-height: var(--m-hit); }
  /* The share sheet's 4-up link grid was 10px/6px padding at 12.5px type. */
  .app-target--link { min-height: var(--m-hit); }
  .app-seg__btn { min-height: var(--m-hit); }
  /* ⛔ SPECIFICITY, NOT LOAD ORDER — the one place this file's header claim was wrong.
     `app.css:767` sets `.pd-row__acts .po-cta--sm { min-height: 40px }` (0-2-0) inside
     its OWN max-width:480px block, and 0-2-0 beats a bare `.po-cta--sm` (0-1-0) however
     late this file loads. The two controls it governs are "Log payment" and "Mark
     missed" — the exact pair this milestone is about — and they measured 117×40 while
     §3 claimed a 44px floor. Matched specificity, not `!important`. */
  .pd-row__acts .po-cta--sm { min-height: var(--m-hit); }
  /* The topbar "Sign in" measured 77.6×35 directly above a tab row raised to 58. */
  .po-acct-btn.po-acct-signin { min-height: var(--m-hit); }
  /* The extra-payment slider is a MONEY control with a 22×22 thumb on a 6px track. */
  .web-slider::-webkit-slider-thumb { width: 28px; height: 28px; }
  .web-slider::-moz-range-thumb { width: 28px; height: 28px; }

  /* ── §4 SAFE AREA IN THE APP SHELL ───────────────────────────────────────
     MEASURED: `env(safe-area-inset-*)` appears in account.css, consent.css and
     onboarding.css and NOT ONCE in app.css — the file that owns the topbar, the
     tab bar and every write-loop modal. In standalone/PWA the topbar sat at
     y 14-35px, inside the Dynamic Island.                                     */
  .web-topbar { padding-top: calc(var(--m-safe-t) + 8px); }
  .app-toast-host { bottom: calc(16px + var(--m-safe-b)); }

  /* ── §5 PRIMARY ACTION ───────────────────────────────────────────────────
     MEASURED: `.po-cta` "Log a payment" was 176.8×56 at 393 and 176.7×76 at
     375 — same width, 20px taller, i.e. the label WRAPPED on iPhone SE. A
     wrapped primary label is the clearest below-bar tell in the product.      */
  /* ⛔ `white-space: nowrap` WAS HERE AND IT IS GONE. It was added to stop the dashboard
     CTA wrapping to two lines at 375 — and it did, by making the button UNSHRINKABLE,
     which is a different thing. ⛔ MEASURED by the independent gate on 8d950ea6, in the
     ONE state that exposes it (signed in and NOT entitled, `.app-page--readonly`):
     `.web-hero-actions` is a `flex-wrap:nowrap` row with `min-width:auto`, whose two
     `.po-cta--lg` children measured 203.19 + 173.39 + 10px gap = **386.58px inside a
     335px `.web-hero` grid box** → `.web-hero` scrollWidth 387 → documentElement
     scrollWidth **407** against clientWidth 375, i.e. Chromium shrink-to-fit at 0.921.
     Isolated three independent ways: a 287-declaration bisect returned exactly ONE hit
     (this one); removing this declaration alone returned 375; removing mobile.css
     returned 375. `viewport-fit=cover` was tested on fresh loads and is NOT the cause.
     ⛔ ANONYMOUS AND ENTITLED DASHBOARDS NEVER SHOWED IT — the same row measures
     330.58px there — which is why eight of my own emulation configurations all read 375
     and I wrongly called the hypothesis falsified. The state was the variable, not the
     harness.
     ⛔ THE REAL FIX FOR THE WRAP IS TO LET THE ROW SHRINK, not to forbid breaking:
     a flex item's default `min-width:auto` refuses to go below min-content, so the row
     demanded its full intrinsic width and the document grew to match. */
  .po-cta {
    min-height: var(--m-control-h);
    letter-spacing: -0.2px;
    min-width: 0;
  }
  /* The dashboard's two-up hero row: let it shrink inside its grid track. Classes are
     the ones the gate read off the RENDERED DOM (`DIV.web-hero` grid / `DIV.web-hero-actions`
     flex), not inferred from source. */
  .web-hero-actions { min-width: 0; }
  .web-hero-actions > .po-cta { min-width: 0; flex: 1 1 0; }
  .app-modal__foot .po-cta { width: 100%; height: var(--m-control-h); font-size: 17px; }
  /* ⛔ NOWRAP SURVIVES ONLY HERE, AND ONLY BECAUSE THIS BUTTON CANNOT WIDEN THE DOCUMENT:
     it is `width:100%` inside `.app-modal`, which is itself bounded by the overlay. The
     hero row above had no such bound, which is the whole difference. */
  .app-modal__foot .po-cta { white-space: nowrap; }
  /* The log sheet's primary label is built as "Log planned payment · $525.00"
     and broke across two lines inside a 270px button. Nowrap plus a slightly
     tighter size keeps it on one line at 375 without changing the string. */
  .app-modal .po-cta { font-size: 16.5px; }

  /* ⛔ DESTRUCTIVE IS NOT THE SAME AS CONSTRUCTIVE. Remove-debt, delete-journey
     and mark-missed all shipped in the same copper as "Save". */
  .po-cta--destructive {
    background: var(--dang-high, #B83030);
    border-color: var(--dang-high, #B83030);
    color: #fff;
  }
  .po-cta--destructive:active { background: #9C2828; }

  /* ⛔ A REFUSED MONEY WRITE IS NOT A FOOTNOTE. `denialSurfaceForResult`
     returns "footer", and the denial copy rendered in `.app-modal__note` —
     12.5px, centred, tertiary grey — identical to "We don't move money…". */
  .app-modal__foot .app-modal__note--denial,
  .app-modal__note[data-denial] {
    color: var(--dang-high, #B83030);
    font-weight: 500;
    text-align: left;
    font-size: 13.5px;
  }

  /* ── §6 INPUTS — ONE HEIGHT ──────────────────────────────────────────────
     MEASURED: 50 / 53 / 53 / 53px in one form — the NAME box 3px shorter than
     the three money boxes. A textbook 2-6px near-miss.                        */
  /* ⛔ `height`, NOT `min-height`. My first cut used `min-height:52px` and the
     boxes grew to 80px with their text stuck at the top — `.app-field__box` is a
     WRAPPER around a prefix, the input and a suffix, so raising its minimum
     inflated the wrapper while the input kept its natural height at the top edge.
     A fixed height plus flex centring makes all four boxes identical AND puts the
     value on the optical centre line. */
  .app-field__box {
    height: var(--m-control-h);
    min-height: 0;
    display: flex;
    align-items: center;
    padding: 0 14px;
    border-radius: 14px;
  }
  .app-field__box > input { height: auto; padding-top: 0; padding-bottom: 0; }
  /* ⛔ ALL-CAPS MICRO ON EVERY FIELD IS THE PRODUCT'S STRONGEST "utility web
     app" SIGNAL. 11px + 1px tracking × four stacked labels. Sentence case at
     13px keeps the hierarchy and drops the density; the all-caps eyebrow is
     reserved for one per surface (the hero label, the paywall eyebrow). */
  .app-field__label {
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: none;
    color: var(--payoff-text-2, #5F6468);
    margin-bottom: 6px;
  }

  /* ── §7 THE PAYWALL ──────────────────────────────────────────────────────
     MEASURED: `.pw` is a full-bleed 393×852 takeover whose content block is
     vertically CENTRED (`.pw__scroll { align-content: safe center }`), leaving
     ~135px of dead ground under the header and ~147px under the legal line,
     with no plan card guaranteed in the first viewport. `paywall.css` has ONE
     media query, at 760px, and no phone rule at all.
     Top-aligning is the whole fix: all three priced cards land above the fold. */
  /* ⛔ THE OVERFLOW IS `.pw`, NOT THE SCROLLER — corrected after measuring twice.
     `paywall.css:58` sets `.pw { min-height: 100% }`, and with `.pw__wrap`'s
     34/56px vertical padding the surface grew TALLER THAN THE VIEWPORT at 375×667,
     pushing `.pw__eyebrow` to a NEGATIVE top — clipped under the fixed bar — while
     `scrollTop` was pinned at 0, so it could not be scrolled back into view either.
     (The exact pixel figures depended on the content on screen and are deliberately
     not restated here; the cause is the `min-height` and the padding, not a number.) Capping the surface to the dynamic viewport
     and letting its `flex:1` child actually shrink (`min-height:0`) is the fix;
     `align-content: start` alone was not.
     ⛔ CORRECTED TWICE, AND THE SECOND CORRECTION IS THE POINT. This block first read
     "MEASURED after: eyebrow top 20px, scroller 667px" — numbers from the FIRST,
     REJECTED cut. Its replacement then asserted `.pw__scroll` scrollHeight 692 and
     `.pw` top 0; the independent gate measured **598** and **57**. Both were mine, and
     both were state-dependent numbers written as if they were properties of the rule.
     ⛔ WHAT IS STATED HERE NOW IS ONLY WHAT THE RULE ITSELF DETERMINES, independently
     reproduced at 375×667: `.pw__eyebrow` is **not clipped** (top > 0, measured 89
     relative to `.pw`), `.pw__scroll` has `overflow-y: auto` and a clientHeight bounded
     by the viewport rather than by content (**598**), and all three priced cards land
     in the first viewport. ⛔ SCROLLHEIGHT AND `.pw` TOP ARE DELIBERATELY NOT CLAIMED:
     they move with content, with the step on screen and with whatever chrome sits above
     the overlay, so any number for them is a fact about one walk, not about this file. */
  .pw { height: 100dvh; min-height: 0; max-height: 100dvh; }
  .pw__scroll { align-content: start; min-height: 0; overflow-y: auto; }
  .pw__wrap { padding-top: 20px; padding-bottom: calc(24px + var(--m-safe-b)); }
  .pw__bar { padding-top: calc(var(--m-safe-t) + 12px); }
  .pw__back { min-height: var(--m-hit); display: inline-flex; align-items: center; }
  /* The plan card IS the button (`paywallSurface.js:596`), so the hit floor and
     the price legibility both belong to the card. */
  .pw-plan { min-height: 76px; border-radius: 16px; }
  .pw-plan__name { font-size: 17px; }

  /* ── §8 RESTRAINT ────────────────────────────────────────────────────────
     The log sheet nested three rounded containers: `.app-modal` (r22) → a
     tinted summary card (r16) → the CTA (r15). One level is the bar.          */
  .app-modal__body > .app-pay-summary,
  .app-modal__body > [class*="summary"] {
    background: transparent;
    border: 0;
    border-radius: 0;
    padding-left: 0;
    padding-right: 0;
  }

  /* ── §9 CONSENT — ONE PRIMARY ────────────────────────────────────────────
     MEASURED: the banner occupies ~24% of a 393×852 viewport and renders
     "Allow" and "Decline" as two equal-weight copper fills — two primaries on
     a consent choice.                                                         */
  .consent-btn + .consent-btn,
  .consent-btn--decline {
    background: transparent;
    color: var(--payoff-text-2, #5F6468);
    border: 1px solid var(--payoff-border, #DDD7CF);
  }
  .consent-btn { min-height: var(--m-hit); }
}

/* ============================================================================
   §10 NEW ELEMENTS ONLY — the Mark-missed confirmation (MM-CONFIRM).

   ⛔ THESE RULES ARE NOT INSIDE THE 480px BREAKPOINT, AND THAT IS ARGUED RATHER
   THAN ASSUMED. Every selector below names a class that did not exist before
   this change (`.app-missed*`, `.po-cta--destructive`, `.app-overlay--missed`).
   No element in the desktop baselines carries any of them, so they cannot move
   a desktop pixel — while a confirmation that only styled itself on a phone
   would render its destructive action in ordinary copper on a laptop, which is
   the exact defect this sheet exists to close.
   ========================================================================== */
.po-cta--destructive {
  background: var(--dang-high, #B83030);
  border-color: var(--dang-high, #B83030);
  color: #FFFFFF;
}
.po-cta--destructive:hover { background: #9C2828; border-color: #9C2828; }
.po-cta--destructive:active { background: #8E2323; }
.po-cta--destructive:focus-visible { outline: 2px solid var(--dang-high, #B83030); outline-offset: 2px; }

.app-missed__subject {
  font-size: 15px;
  color: var(--payoff-text-2, #5F6468);
  margin: 6px 0 0;
}
/* Reuses `.app-modal__advisory`'s amber plate — the product's existing
   "read this before you act" treatment — rather than inventing a second one. */
.app-missed__warn { margin-top: 12px; }
.app-missed__clearing {
  text-align: left;
  margin-top: 10px;
  color: var(--payoff-text-3, #9E9A94);
}
