/* The app shell: the header, the views, the update notice and the desk's
 * three panes.
 *
 * Consumes tokens.css and restates nothing. No colour literal, no second
 * definition of --touch, no copy of .btn or .field, and no CSS framework
 * (HARD RULE 8) — this file is only what routing added.
 *
 * Split in increment S (it stood at 764 of its 800 lines): the phone's tab
 * bar is tabs.css and the Settings cards are settings-cards.css, both linked
 * straight after this file so the cascade is what it was. docs/increments/S.md
 * accounts for every line.
 *
 * Two standing rules it must not break:
 *
 *   1. NOTHING here declares `display` on `[data-view]`. app.css's
 *      `[hidden] { display: none !important }` is what makes the router's
 *      `hidden` mean hidden, and an author `display` on the same element is
 *      exactly the trap that rule exists for — it is how the old Locked card
 *      stayed on screen after every successful unlock.
 *      AMENDED for day-page A: day.css declares one, GUARDED by
 *      `:not([hidden])` — the [hidden] rule's !important still wins, so the
 *      router's `hidden` stays in charge. The guard is the licence; an
 *      unguarded display on [data-view] is still the trap.
 *
 *   2. NOTHING here grows a `transform`, `filter`, `backdrop-filter`,
 *      `perspective`, `contain: paint/layout` or a `will-change` on any of
 *      them, on .shell or on a view wrapper. Any one of those makes that
 *      ancestor the containing block for every fixed descendant, and #keypad's
 *      `position: fixed; inset: 0` would re-anchor itself inside the shell's
 *      42rem column. capture.css:7-10 states the same rule for the sticky bar.
 *      Also (day-page A): no `overflow` other than visible, and no
 *      `transform`, `filter`, `contain` or `perspective`, on .shell, a view
 *      wrapper or .day — the dock and the tabs are sticky to the PAGE's
 *      scroll, and an overflow ancestor would quietly become their scrollport.
 */

/* ------------------------------------------------------------------- header */

.shell__tools {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 1 auto;
  min-width: 0;
}

/* The pill gives way before Settings does. Increment E's photo count can make
 * it long (`update available · 12 photos waiting`), and at 360px it pushed
 * Settings into the side margin and then past the edge (fidelity review of E,
 * L10). It shrinks and ends in an ellipsis; its words stay whole in the
 * button for a screen reader. app.css holds the pill's other rules and is not
 * edited again in the rebuild (DAY-PAGE-PLAN §0.5). */
.sync-pill {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* F8: the synced pill's wash has a dark value (tokens.css --tint-chosen). In
 * light mode it is app.css's --ps-green-tint exactly; in dark mode --ok on the
 * light tint was 2.04:1, and is 5.85:1 on this one. */
.sync-pill.is-synced {
  background: var(--tint-chosen);
}

/* S: app.css is frozen (DAY-PAGE-PLAN §0.5), so its four sizes under 1rem
 * take their step of the type scale here (tokens.css --text-*): on a phone
 * the pill and a field's label are 14 px where they were 13, and the sign-in
 * panel's sentences 16 where they were 15. */
.sync-pill,
.field__label {
  font-size: var(--text-sm);
}

.panel__lede,
.panel__error {
  font-size: var(--text-base);
}

/* S, step 2: and the panel's sentences keep to the measure (tokens.css), as
 * a Settings card's do (settings-cards.css): 92 characters a line at 660px.
 * Below the desk only, as there. */
@media (max-width: 1099.98px) {
  .panel__lede {
    max-width: var(--measure);
  }
}

.shell__nav {
  display: flex;
  gap: var(--space-1);
}

.shell__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch);
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--ink-muted);
  font-size: var(--text-md);
  font-weight: 600;
  text-decoration: none;
}

.shell__link:hover {
  background: var(--surface-sunken);
  color: var(--ink);
}

.shell__link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.shell__link[aria-current="page"] {
  background: var(--surface-sunken);
  color: var(--accent);
}

/* The fleet board is a desktop page (§3.8 "Extra pages"). `display: none`, not
 * a visual trick: a link merely pushed off-screen is still in the tab order and
 * still read out, which is worse than not being there.
 *
 * The number is written out because a media query cannot read a custom
 * property — `var(--bp-desktop)` is 1100px in tokens.css and is unusable here.
 * capture.css:569 does the same, for the same reason. */
.shell__link--fleet {
  display: none;
}

@media (min-width: 1100px) {
  .shell__link--fleet {
    display: inline-flex;
  }
}

/* -------------------------------------------------------------------- views */

.view__title {
  margin: var(--space-6) 0 var(--space-4);
  font-size: 1.35rem;
  line-height: 1.2;
  letter-spacing: -0.015em;
}

/* Focused by the router on every navigation so a screen reader is told where it
 * landed. tabindex="-1" in the markup is what makes .focus() do anything at
 * all; a browser shows no ring for a programmatic focus, and this is only for
 * the owner who arrived by keyboard. */
.view__title:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}

/* What a page that is not built yet says. It says exactly that. */
.view__stub {
  margin: 0;
  padding: var(--space-4);
  border-left: 3px solid var(--ps-lime);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--surface-sunken);
  color: var(--ink-muted);
  font-size: var(--text-base);
}

/* ---------------------------------------------------------- the shell column */

/* The tab bar's half of .shell (the bar itself is tabs.css), and it lives
 * here rather than in app.css because the tab bar is the only reason for it: a column tall enough to reach the
 * bottom of the screen, so `margin-top: auto` below can push the bar down
 * there. Without it, Pending — one honest sentence — left the tab bar floating
 * in the middle of an empty phone screen, because `position: sticky` has
 * nothing to stick to on a page shorter than the viewport.
 *
 * `display: flex` is safe for #keypad: a flex ancestor is not a containing
 * block for a fixed descendant. A `transform` or a `filter` here would be.
 * The subtraction is body's own padding (app.css), and dvh rather than vh so
 * the phone's collapsing address bar does not leave a gap. */
.shell {
  display: flex;
  flex-direction: column;
  /* min-height comes from the two width ranges below: the phone subtracts its
   * slim body padding-top, the desk its larger one; padding-bottom is 0
   * everywhere since the tab bar and the dock own the foot. */
}

body {
  padding-bottom: 0;
}

/* The dock gives Today its foot spacing; every other view keeps a readable
 * gap between its last line and the tab bar. */
.view:not([data-view="today"]) {
  padding-bottom: var(--space-8);
}

/* The phone starts at the date (MOCKUP.html): the app title row is hidden,
 * the green rule goes, and More (more-menu.css; it was the Settings link
 * until increment S), the tray and the sync pill keep one slim right-aligned
 * 44px tools row above it. */
@media (max-width: 1099.98px) {
  body {
    padding-top: var(--space-2);
  }

  .shell {
    min-height: calc(100dvh - var(--space-2));
  }

  .shell__titles {
    display: none;
  }

  .shell__head {
    justify-content: flex-end;
    padding-bottom: 0;
    border-bottom: 0;
  }
}

@media (min-width: 1100px) {
  .shell {
    min-height: calc(100dvh - var(--space-6));
  }
}

/* ---------------------------------------------------- the update notice (F9)
 *
 * sw-update.js: a new version of the diary took over this page. It wears the
 * toast's box (day.css `.toast`: just above the dock, or the tabs where there
 * is no dock) one layer UNDER the toast, so an Undo is never hidden behind it;
 * it stays until Reload. Its words are the toast's (`.toast__text`); Reload
 * is a text button in the structural green, as the toast's Undo is, and .btn
 * carries its 44 px target. */
.toast.update-note {
  z-index: 9;
}

.update-note__reload {
  flex: none;
  padding: 0 var(--space-3);
  border-color: transparent;
  background: transparent;
  color: var(--accent);
  font-weight: 700;
}

/* While it shows, the page has its height as room at the foot, or it would
 * lie for good over the day's newest card (review of F, round 1: 17:05 Chased
 * at 546–592 under the notice at 538–592, at 390 × 844, with no way to scroll
 * it clear). sw-update.js sets data-update-note and --update-note-h on <html>;
 * the fallback is its one-row height. On Today the room goes under the last
 * thing drawn above the dock — the timeline, or the "hidden by the filter"
 * line when it shows — so at the end that sits a gap above the notice; every
 * other view adds it to its foot gap above the tab bar. */
:root[data-update-note] .today__list:has(+ .day__hidden[hidden]),
:root[data-update-note] .day__hidden:not([hidden]) {
  margin-bottom: calc(var(--update-note-h, 3.5rem) + var(--space-2));
}

:root[data-update-note] .view:not([data-view="today"]) {
  padding-bottom: calc(var(--space-8) + var(--update-note-h, 3.5rem) + var(--space-2));
}

/* ----------------------------------- L: the desktop's three panes (≥ 1100px)
 *
 * MOCKUP.html:165-188, the APPROVED desktop wireframe: calendar pane · the
 * day · context pane, drawn 118 / 270 / 132 inside a ~520px frame. The same
 * ratio here as fr units, so the left keeps ~22%, the centre ~53% and the
 * right ~25% of the shell at any width. Below 1100px NOTHING in this block
 * applies: the phone keeps its tabs and its one column exactly as shipped.
 *
 * The shell is widened HERE, not in app.css (frozen since day-page A): the
 * later file wins the .shell tie. Grid, not flex: the header spans the row,
 * every view lands in the centre column, and the two <aside class="pane">
 * elements (index.html) take the sides. The panes are SECOND MOUNTS filled
 * by desktop.js — the router goes on hiding [data-view] wrappers and never
 * learns the panes exist. No transform, filter or overflow appears on .shell
 * or a view wrapper (standing rules 1 and 2 above); the panes' own
 * overflow-y is theirs alone and holds no fixed or sticky descendant. */

.shell__link--desk {
  display: none;
}

@media (min-width: 1100px) {
  /* The bar's absence here (`.tabs { display: none }`, --tabs-h: 0px) is
   * tabs.css's, with the rest of the bar.
   *
   * The header nav's desk links (Pending, Search, Entities) are added by
   * desktop.js only at this width, so a phone's bare a[href='#/pending'] is
   * still the tab bar's; the rule still says "desk only" so a window shrunk
   * below 1100px hides them before the JS has heard the media change. */
  .shell__link--desk {
    display: inline-flex;
  }

  .shell {
    display: grid;
    grid-template-columns: minmax(0, 118fr) minmax(0, 270fr) minmax(0, 132fr);
    grid-template-rows: auto minmax(0, 1fr);
    column-gap: var(--space-4);
    align-items: start;
    max-width: 76rem;
  }

  .shell__head {
    grid-column: 1 / -1;
  }

  .shell > .view {
    grid-column: 2;
    grid-row: 2;
    min-width: 0;
  }

  /* The side panes scroll inside themselves and stay put while the day
   * scrolls — the wireframe's window. Sticky, never fixed (the containing-
   * block rule at the top of this file). */
  .pane {
    grid-row: 2;
    min-width: 0;
    position: sticky;
    top: var(--space-2);
    max-height: calc(100dvh - var(--space-6));
    overflow-y: auto;
  }

  .pane--left {
    grid-column: 1;
  }

  .pane--right {
    grid-column: 3;
  }

  /* The Fleet board is §3.8's "extra page", not the wireframe's desk: it
   * keeps the full 64rem row it has had since I1 (entities.css), with the
   * panes out of its way. Settings, Recovered and the Sync audit keep the
   * panes: their cards fit the centre, and the left pane's month grid is
   * the desk's one way back to a day.
   * Wave 13 integration: the Month ledger (ML) is the same kind of page —
   * day groups beside two totals tables, drawn for the whole row (its 72rem
   * cap is ledger.css's) — so it gets the same treatment; in the centre
   * column its rows broke to one letter a line. */
  .shell:has(> .view[data-view="fleet"]:not([hidden])) > .pane,
  .shell:has(> .view[data-view="ledger"]:not([hidden])) > .pane {
    display: none;
  }

  .shell:has(> .view[data-view="fleet"]:not([hidden])) > .view,
  .shell:has(> .view[data-view="ledger"]:not([hidden])) > .view {
    grid-column: 1 / -1;
  }
}

/* ------------------- S: the side column (720px to 1099.98px, never a phone)
 *
 * The owner on his Android phone, 2026-10-10 ("the scaling to the screen size
 * have to be worked on"), and the review's "nothing between 600 and 1100 px":
 * a tablet, an unfolded foldable or a wide window got the phone's 42rem
 * column with empty margins. From 720px it gets TWO COLUMNS that fill the
 * width — the desk's LEFT pane (the month with its move days, "Planned today
 * · N", "Pending by customer") and the page beside it. The tabs stay, More
 * stays, and nothing else of the desk is here: no right pane, no centre
 * extras, no desk nav links.
 *
 * desktop.js decides (desktop-spec.js paneTier) and unhides #pane-left; every
 * rule below hangs off that one fact, so the stylesheet and the script cannot
 * disagree. A phone on its side is 720px wide and more and is NOT given the
 * column — its pane stays hidden, and it keeps the phone's one column.
 *
 * HOW THE COLUMN STANDS, and why it is not the desk's grid. Below 1100px the
 * shell is the phone's flex column (above): the Today view grows to the foot,
 * the dock is sticky on the tab bar, the bar is sticky on the page and takes
 * its own room — and gives it back when capture.css takes the bar away while
 * a line is typed. Three other files stand on those mechanics. A grid would
 * have needed a row kept for the bar, and a copy of capture.css's own rule to
 * give that row back. So the shell STAYS that column, the views take a left
 * margin of the pane's width, and the pane hangs in the margin: sticky, as
 * tall as the window allows, and — by a bottom margin of minus its own
 * height — taking no room of its own in the column. Nothing about the dock,
 * the bar, a sheet or the keyboard is different from the phone's.
 *
 * No transform, filter or overflow appears on .shell or a view wrapper
 * (standing rules 1 and 2 above); the pane's own overflow-y is its alone and
 * holds no fixed or sticky descendant. */

@media (max-width: 1099.98px) {
  /* The desk's alone. desktop.js hides it; this says so before the script
   * has heard that the window shrank (as .shell__link--desk above). */
  .pane--right {
    display: none;
  }
}

@media (min-width: 720px) and (max-width: 1099.98px) {
  /* The pane's width: 5 parts of 13, and never under 22rem — at 22rem its
   * month's days are 44.9px wide, a finger's target (calendar.css). At 720px
   * that leaves the day 320px (a 360px phone gives it 328); at 1099px the pane
   * is 404px and the day 647px, which keeps a card's line to about 75
   * characters. `vw`, not `%`: the toast below is fixed to the window and
   * must find the same number. 3 x --space-4 is the page's two gutters
   * (app.css) and the gap between the columns.
   *
   * The pane's height: the window, less the page's top gutter and the tab
   * bar with a gap above it — so all of it shows, and what does not fit
   * scrolls inside it. `dvh`: it follows the keyboard. */
  .shell:has(> .pane--left:not([hidden])) {
    --side-w: max(22rem, (100vw - 3 * var(--space-4)) * 5 / 13);
    --side-h: max(12rem, 100dvh - var(--tabs-h) - var(--space-2) - var(--space-4));
    --side-gap: calc(var(--side-w) + var(--space-4));
    max-width: none;
  }

  /* The Fleet board and the Month ledger are §3.8's "extra pages", drawn for
   * the whole row: the pane steps aside for them here as both panes do on
   * the desk (below 1100px they are reached from More). */
  .shell:has(> .view[data-view="fleet"]:not([hidden])),
  .shell:has(> .view[data-view="ledger"]:not([hidden])) {
    --side-gap: 0px;
  }

  .shell:has(> .view[data-view="fleet"]:not([hidden])) > .pane--left,
  .shell:has(> .view[data-view="ledger"]:not([hidden])) > .pane--left {
    display: none;
  }

  /* The header's row of tools belongs to the page's column too, so the pane
   * starts at the top of the window, level with them, not under 44px of
   * nothing. (--side-gap is unset without the pane: no margin at all.) */
  .shell > .shell__head,
  .shell > .view {
    margin-left: var(--side-gap, 0px);
  }

  /* The pane comes after the header in the column, so its top margin draws
   * it up by the header's one row (the tools are --touch tall), and its
   * bottom margin takes back the rest of its height: the two margins and
   * the height come to nothing, and the page under it starts where it
   * would have. Sticky at the page's top gutter, where it already stands,
   * so it never moves. */
  .pane--left {
    position: sticky;
    top: var(--space-2);
    flex: none;
    align-self: flex-start;
    width: var(--side-w);
    height: var(--side-h);
    margin-top: calc(-1 * var(--touch));
    margin-bottom: calc(var(--touch) - var(--side-h));
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* The toast (day.css) and the update notice that wears its box are fixed
   * to the window: they keep to the page's column, above the dock they are
   * anchored to, rather than straddling the pane. */
  .shell > .toast {
    left: calc(var(--space-4) + var(--side-gap, 0px));
  }

  /* Beside the Calendar tab's own month the pane's would be a second one,
   * each with its own arrows; the two lists stay. (The desk has no Calendar
   * link in its header — its pane IS the calendar.) */
  .shell:has(> .view[data-view="calendar"]:not([hidden])) .pane__block--month {
    display: none;
  }
}
