/* MOBILE LAYER — chrome
   Every rule below is inside a max-width:700px (or pointer:coarse) query and
   answers a measured defect found reviewing the product on a 390x844 phone.
   Desktop must not be able to see this file. See web/styles/mobile.css for the
   specificity contract. */

/* ============================================================ 1. THE LEDGER */
/* The bottom chrome was three independent literals — `.tabbar` padded with
   env(safe-area-inset-bottom), `.main` reserved a flat 76px, and the FAB was
   parked at 76px + the inset. On a phone with a 34px home indicator the bar
   measured ~106 CSS px against 76px reserved, so the last 30 CSS px of every
   scroll container sat permanently under opaque chrome. One number now drives
   all three, and the inset is part of it. [defects 2, 3]

   --sab is env() behind a custom property on purpose: it is the only way to
   put a phone's home indicator into a test. Set --sab to 34px in devtools and
   the whole bottom stack moves as one, which is exactly the failure the
   literal 76px hid. */
@media (max-width: 700px) {
  :root {
    --sab: env(safe-area-inset-bottom, 0px);

    /* Row height without the inset: a 26px glyph box + 2 + a 14px label line
       is 42 of ink in a 60px row. The 72px the bar used to measure was that
       same ink under a 22px line box for a 10px label — leading nobody chose,
       not deliberate space. */
    --tabbar-row: 60px;
    --tabbar-h: calc(var(--tabbar-row) + var(--sab));

    /* The disc cleared the bar by 4 CSS px (disc bottom 767.5, bar top 772):
       two fixed layers welded into one blob with no thumb dead zone between
       them. HIG and Material both want 16; 18 also lands the disc on whole
       pixels at both densities. [defect 3] */
    --fab-size: 58px;
    --fab-gap: 18px;
    /* What the scroll container has to hold back for the disc: the gap above
       the bar, the disc itself, and a line of air so the last row of content
       does not kiss it. [defect 1] */
    --fab-lane: calc(var(--fab-gap) + var(--fab-size) + var(--s-5));
  }
}

/* ===================================================== 2. THE SCROLL BOTTOM */
/* app.css reserved `padding-bottom: 76px` against a bar that is 60 + inset
   tall and a disc that floats another 76 above it. Both numbers are now
   derived, so a screen can end where the reader can actually reach it.
   [defects 1, 2] */
@media (max-width: 700px) {
  html .main { padding-bottom: calc(var(--tabbar-h) + var(--fab-lane)); }

  /* /settings and /messages cannot compose a post, so app.js does not render
     the disc there (it was covering 41 of the 78 CSS px of the destructive
     "Sign out" button — lime pixels sat directly on the red label). With no
     disc there is no lane to reserve: give the bar its clearance and stop. */
  html .shell--nocompose .main { padding-bottom: calc(var(--tabbar-h) + var(--s-6)); }

  /* /messages is a third case: not a scroll that has to stop short of the bar,
     but a fixed frame that scrolls inside itself (see
     web/styles/views/messages.css). Reserving a lane under it is 84px of empty
     document below a screen that already fits; the frame subtracts the bar
     instead. Without this the composer sat at the bottom of the window with the
     bar drawn over it — measured 747..812 against a bar at 752..812, so 60 of
     its 65px, the entire text field, were underneath. */
  html .shell--nocompose .main:has(.dm) { padding-bottom: 0; }
  html .dm { --dm-h: calc(100dvh - var(--tabbar-h)); }
  /* The bar directly below now carries the home indicator's inset, so the
     composer must not carry a second copy of it. */
  html .thread__composer { padding-bottom: var(--s-5); }
}

/* ======================================================= 3. THE TAB BAR */
@media (max-width: 700px) {
  /* Height and safe area come off the ledger: the row occupies what is left
     above the home indicator rather than being padded away from it. */
  .tabbar {
    height: var(--tabbar-h);
    padding: 0 0 var(--sab);
    /* stretch, not centre: the cell is the hit target, so it takes the whole
       row height (59 CSS) rather than shrinking to the 42 CSS its glyph and
       label happen to occupy. Apple's floor is 44. */
    align-items: stretch;
  }

  /* [defect 4] Measured bar fill rgb(12,13,10) against feed fill rgb(11,13,9)
     — 1.002:1. --glass-strong is rgba(11,13,10,.86) sitting on a surface of
     essentially the same value, so `blur(24px) saturate(180%)` was paying GPU
     time to composite a layer nobody could see, and a bright photo scrolling
     under still read through at 14%. A bar the whole feed passes beneath is
     not glass, it is a layer: an opaque fill one surface step up (--bg-3, the
     token for overlays and menus, ~4% lighter than the column), the strong
     hairline instead of the faint one, and a shadow cast upward onto the page
     so the page reads as being behind it. Blur is dropped, not weakened —
     there is nothing left for it to do. */
  html .tabbar {
    background: var(--bg-3);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-top: 1px solid var(--line-strong);
    box-shadow: 0 -10px 28px rgba(0,0,0,.55), 0 -1px 0 var(--ink-1);
  }

  /* [defect 5] Six cells across 374px of content box resolved to a 62.33 CSS
     pitch, so icon centres 3, 5 and 6 landed on ⅓-device-pixel offsets and
     every glyph in them was resampled. Rounding the track down to whole pixels
     and centring the row leaves at most 5px of slack, split two ways: centres
     land on .0 or .5 CSS — whole device pixels at DPR 2 — at every width. */
  .tabbar { grid-auto-columns: round(down, calc(100vw / 6), 1px); justify-content: space-evenly; }
  /* Signed in the row is six cells and the slack is under 5px, so centring it
     is what puts the centres on whole pixels. Signed out two cells are gated
     away and four fixed cells centred would read as a block shoved into the
     middle of the bar, so that state spaces them instead. */
  .tabbar:has(.tabbar__item:nth-child(6)) { justify-content: center; }
  @supports not (width: round(down, 10.5px, 1px)) {
    .tabbar, .tabbar:has(.tabbar__item:nth-child(6)) { grid-auto-columns: minmax(0, 1fr); justify-content: stretch; }
  }

  /* One glyph row for all six cells, so the labels sit on one line whatever
     the glyph inside measures. */
  .tabbar .tabbar__glyph { height: 26px; width: 26px; }
  .tabbar .tabbar__item { padding: 0; gap: 2px; }
  .tabbar .tabbar__label { font-size: 10px; line-height: 14px; }

  /* [defect 5] Inside a nominal 23px box the ink ran Home 19, Explore 19,
     Grow 16, Alerts 17 CSS — a 19% spread in a row that has to read as one
     set — and the "You" avatar rendered 24.5–25, ~32% larger than everything
     beside it. Each glyph gets the render size that puts its *ink* in a common
     ~19.5px optical box, measured across both the outline and the solid
     (active) path, because the active cell swaps artwork. The avatar is a
     filled disc and carries the most mass, so it sits at 20 with the ring
     moved inside it — the old ring was an outer shadow and added 2px to the
     circle it was supposed to describe. */
  .tabbar .tabbar__item[href="/"] .tabbar__glyph svg           { width: 23.5px; height: 23.5px; }
  .tabbar .tabbar__item[href="/explore"] .tabbar__glyph svg     { width: 24px;   height: 24px; }
  .tabbar .tabbar__item[href="/diaries"] .tabbar__glyph svg     { width: 24px;   height: 24px; }
  .tabbar .tabbar__item[href="/notifications"] .tabbar__glyph svg { width: 24px; height: 24px; }
  .tabbar .tabbar__item[href="/messages"] .tabbar__glyph svg     { width: 25.5px; height: 25.5px; }
  .tabbar .tabbar__more .tabbar__glyph .avatar {
    width: 20px; height: 20px;
    box-shadow: inset 0 0 0 1px var(--line-strong);
  }
  .tabbar .tabbar__more .tabbar__glyph > svg { width: 22px; height: 22px; }
}

/* ============================================================== 4. THE FAB */
@media (max-width: 700px) {
  /* [defects 1, 3] The disc measured 316→373.5 x 710→767.5: 4px above the bar
     and on top of whatever the page happened to be showing — three consecutive
     body lines of the feed, a reply's timestamp, the whole "+%" cell of row 3
     of Rising now, a diary entry's date. Three things change:
       · it is not rendered where a post cannot be composed (app.js),
       · `.main` reserves its lane, so no screen *ends* underneath it,
       · it tucks the moment the reader scrolls down, and comes back the
         moment they scroll up (app.js toggles .is-fab-tucked).
     Reading is a downward gesture, so the disc is gone for the whole of it. */
  html .fab {
    bottom: calc(var(--tabbar-h) + var(--fab-gap));
    right: max(var(--s-6), env(safe-area-inset-right, 0px));
    width: var(--fab-size); height: var(--fab-size);
    transition: transform var(--d-3) var(--ease-spring), opacity var(--d-2) var(--ease-out);
  }
  /* Translated by its own height plus everything below it, so it leaves the
     screen rather than sinking into the bar. */
  html.is-fab-tucked .fab {
    transform: translateY(calc(var(--tabbar-h) + var(--fab-gap) + var(--fab-size)));
    opacity: 0;
    pointer-events: none;
  }
}

/* ==================================================== 5. THE COMPOSE SHEET */
/* [defect 6] There was no max-width:700px rule for `.modal` or `.modal-scrim`
   anywhere, so compose was the desktop dialog: a 358 x 242.5 card pinned 68
   CSS from the top with 533.5 CSS — 63% of the screen — of scrim under it, and
   the primary "Post" pill 246→285.5 from the top of the screen: out of thumb
   reach and exactly where the software keyboard lands. On a phone compose owns
   the screen. The sheet is scoped to the composer by :has() so the small
   confirm dialogs (profile, diaries, diary, the shortcut sheet) keep the
   centred treatment that fits their content. */
@media (max-width: 700px) {
  html .modal-scrim:has(.composermodal) {
    /* One number for the pinned action bar: the sheet body holds back exactly
       this much so nothing can hide underneath it. */
    --sheet-bar-h: 60px;
    /* And one for the air beneath the controls. The bar used to end on the very
       last pixel of the sheet, which on a phone is the pixel the browser's own
       bottom chrome is drawn over; "Post" read as sitting on the lip of the
       screen. This is the margin, carried by the bar's own padding so the bar
       still reaches the bottom edge and there is no seam under it. */
    --sheet-bar-air: 22px;
    /* Everything the bar occupies. Stated once, spent in three places: the
       bar's height, the body's held-back padding, and the bar's own padding. */
    --sheet-bar-lane: calc(var(--sheet-bar-h) + var(--sheet-bar-air) + var(--sab));
    padding: 0;
    place-items: stretch;
    background: var(--bg-1);
    /* The sheet is opaque and full-bleed: there is no page left to blur. */
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    overflow: hidden;
  }

  html .modal-scrim:has(.composermodal) .modal {
    max-width: none; width: 100%; height: 100dvh;
    display: flex; flex-direction: column;
    border: 0; border-radius: 0;
    background: var(--bg-1);
    box-shadow: none;
    /* A sheet arrives from the edge it is attached to. The scale-and-drift of
       a centred dialog reads as a mistake at full bleed. */
    transform: translateY(3%);
  }
  /* translateY(0) AND NOT `none`, AND THAT IS THE WHOLE BUG.

     The action bar below is `position: fixed`, and a fixed box is measured
     against the layout viewport — which on a phone is the LARGE viewport, the
     one that runs on behind the browser's own bottom chrome. The sheet is
     `100dvh`, the dynamic one, which stops above that chrome. The two agree
     only on a desktop window, which is why this looked right in every test and
     wrong on every phone: while the URL bar is showing, the bar was pinned
     somewhere below the sheet it belongs to. Measured here by shrinking the
     sheet to 712 in an 812 window — exactly the ~100px a browser's chrome
     takes: the sheet ended at 712 and "Post" sat at 752..812, a whole bar's
     height past the last pixel the member can see.

     A transform makes this element the containing block for fixed descendants,
     so the bar is measured against the SHEET. Same experiment with the
     transform in place: bar at 652..712, flush with the sheet's own bottom.
     `none` is the one value that does not do this, so the open state keeps an
     identity translate — visually the same end point the transition was always
     travelling to. */
  html .modal-scrim.is-open:has(.composermodal) .modal { transform: translateY(0); }

  /* The body scrolls; the action bar does not. Its padding holds back exactly
     the lane the bar occupies so nothing can hide underneath it. */
  html .modal-scrim:has(.composermodal) .composermodal {
    flex: 1; min-height: 0;
    display: flex; flex-direction: column;
    overflow-y: auto; overscroll-behavior: contain;
    padding-bottom: var(--sheet-bar-lane);
  }

  /* [defect 7] In the card the header row sat on a 25 CSS inset and the body
     on 21, and the divider started at the text column (x=93) but stopped on
     the body inset (x=353), aligning to neither. One inset now: --s-6 for the
     header, the same --s-6 for the body, and the icon button and the text pill
     are pulled back by their own optical padding so their *ink* starts and
     ends on that line rather than their hit areas. */
  html .modal-scrim:has(.composermodal) .composermodal__bar {
    padding: var(--s-5) var(--s-6);
    padding-top: max(var(--s-5), env(safe-area-inset-top, 0px));
  }
  /* -13px, not -8: the close glyph's ink is 9.5 CSS inside a 36px hit area, so
     the button has to be pulled back by its own optical padding (13.25) for
     the ink to start on the 16px line the avatar below it starts on. The hit
     area keeps its full 36px and simply runs closer to the screen edge. */
  html .modal-scrim:has(.composermodal) .composermodal__bar .iconbtn { margin-left: -13px; }
  html .modal-scrim:has(.composermodal) .composermodal__drafts { margin-right: -12px; }
  html .modal-scrim:has(.composermodal) .composer__inner { padding: 0 var(--s-6); }
  html .modal-scrim:has(.composermodal) .composermodal__context { padding: 0 var(--s-6); }

  /* The sheet is 844 tall; a 110px textarea in it would be a card floating in
     an empty screen, with the audience and diary chips stranded halfway down.
     The form is a column, the writing surface takes every pixel the screen has
     spare, and the chips ride at the bottom of it just above the action bar.
     `flex: 1 0 auto` and not `1 1 auto`: once the text outgrows the screen the
     textarea keeps its measured height and the sheet body scrolls, rather than
     the textarea being squeezed and clipping its own content. */
  html .modal-scrim:has(.composermodal) .composer__inner { display: flex; flex-direction: column; flex: 1; min-height: 0; }
  html .modal-scrim:has(.composermodal) .composer__row { flex: 1; min-height: 0; }
  html .modal-scrim:has(.composermodal) .composer__col { display: flex; flex-direction: column; gap: var(--s-4); min-height: 0; }
  html .modal-scrim:has(.composermodal) .composer__area { flex: 1 0 auto; min-height: 38dvh; }

  /* Fallout from making the writing surface full height: the global
     :focus-visible ring drew a 2px lime rectangle around 302 x 682 CSS of
     mostly-empty screen, which reads as an error state rather than as focus.
     The indicator moves into the gutter as a 3px rule beside the column — the
     same signal, at the size of a caret rather than the size of the phone. */
  html .modal-scrim:has(.composermodal) .composer__area:focus-visible {
    outline: none;
    box-shadow: -8px 0 0 -5px var(--accent);
  }

  /* The action bar: pinned to the bottom of the sheet, above the home
     indicator, with the divider running the full width of the sheet instead of
     stopping short of both edges. `position: fixed` rather than sticky because
     iOS keeps fixed elements above the software keyboard — the whole point of
     moving the primary action down here. Which box "bottom: 0" means is settled
     by the transform on `.modal` above; without it this is the bottom of the
     browser window rather than the bottom of the sheet.

     The bar reaches the last pixel, but its controls do not: --sheet-bar-air of
     dead space under them, on top of the home indicator's own inset, so the
     primary action is never the thing closest to the edge of the phone. */
  html .modal-scrim:has(.composermodal) .composer__foot {
    position: fixed; left: 0; right: 0; bottom: 0;
    height: var(--sheet-bar-lane);
    padding: var(--s-4) var(--s-6) calc(var(--s-4) + var(--sheet-bar-air) + var(--sab));
    margin: 0;
    background: var(--bg-2);
    border-top: 1px solid var(--line);
    box-shadow: 0 -8px 24px rgba(0,0,0,.45);
    z-index: 2;
  }

  /* [defect 8] The brand accent was spent on the escape hatch and denied to
     the primary action: "Drafts" measured --accent at 15.27:1 while the "Post"
     label was rgb(142,142,138) on rgb(36,44,33) — 4.38:1. A tertiary link
     out-contrasted the primary CTA by 3.5x. Drafts becomes what it is, a quiet
     text link; the disabled Post pill keeps the brand and stays legible, so it
     reads as the primary action waiting for input rather than as a dead
     control. */
  html .modal-scrim:has(.composermodal) .composermodal__drafts {
    color: var(--text-1);
    font-weight: 600;
  }
  html .modal-scrim:has(.composermodal) .composermodal__drafts:hover { background: var(--ink-3); }
  /* The label only, deliberately. app.css states the disabled pill's fill and
     colour with !important, and this layer does not fight !important with
     !important — it addresses a different element instead. The fill stays as
     app.css draws it (rgba(206,255,188,.10) over the sheet, measured
     rgb(36,43,33)); the label is set on `.btn__label`, which owns its own
     colour and so is not reached by an inherited declaration at any weight. */
  html .modal-scrim:has(.composermodal) .composer__actions .btn--primary:disabled .btn__label {
    color: var(--lime-200);
  }
}

/* ================================================ 6. THE BRAND HEADER, ON TOP */
/* `.rail` is `display: none` below 700px and that is the right call — 268px of
   navigation does not fit on a phone. What went with it was the only place the
   product said its own name: every mobile screen opened with a stranger's post
   and no mark anywhere on it.

   `.mhead` (app.css) is the replacement — a fixed bar carrying the mobile logo
   and nothing else. Its height lives here rather than in tokens.css so that
   `top: var(--mhead-h)` on the screen headers below is a no-op on desktop and
   needs no second media query to say so.

   Two tokens, exactly as §1 does it for the tab bar: --mhead-row is the ink,
   --mhead-h is what the layout owes. The notch is folded into the second one so
   that every `top:` offsetting against it clears the status bar for free —
   which is also why the topbar's own `env()` padding is cancelled below. Two
   layers each adding the same inset is how a header ends up 47px too low on a
   notched phone and correct on a rectangular one.

   52px is the tab bar's 60 minus the label line it does not have. It is
   deliberately shorter than --header-h (56): this bar introduces the app, the
   header under it names the screen, and the introduction should not be the
   taller of the two. */
@media (max-width: 700px) {
  :root {
    --mhead-row: 52px;
    --mhead-h: calc(var(--mhead-row) + env(safe-area-inset-top, 0px));
  }

  /* The bar is fixed, so it costs no layout — which means the document has to
     pay the height back or the first line of every screen starts underneath it.
     Written on the same element as §2's padding-bottom and for the same
     reason. */
  html .main { padding-top: var(--mhead-h); }

  /* Four screen headers pin themselves to the top of the viewport, each in its
     own view stylesheet, each loaded by useStyles() AFTER this file — hence the
     `html` prefix, which is this layer's specificity contract (see
     web/styles/mobile.css). Without this they stick at y=0, underneath an
     opaque bar, and the screen loses its title and its back button.

     `.pf-tabs` is the second rank: it already offsets against --header-h
     because it sits below `.pf-top`, so it needs both. */
  html .topbar,
  html .pf-top,
  html .dx-top,
  html .dg-top   { top: var(--mhead-h); }
  html .pf-tabs  { top: calc(var(--header-h) + var(--mhead-h)); }

  /* §12 of m/nav-utility.css gives every sticky header the notch inset, which
     was right while they were the topmost layer. They are not any more — the
     bar above them carries it, and --mhead-h already includes it. */
  html .topbar { padding-top: 0; }
}
