/* Life OS — the phone and tablet layer.
 *
 * ── Why this is a separate file ──────────────────────────────────────────
 *
 * The rule this whole file exists to keep:
 *
 *     MOBILE PRESERVES CAPABILITY AND INFORMATION, NOT DESKTOP GEOMETRY.
 *
 * Not a responsive cleanup of app.css. Life OS on a phone is a different
 * SHAPE of the same application — a bottom navigation instead of a sidebar,
 * a command home instead of a four-bucket board, sheets instead of dialogs,
 * one book page instead of a spread. Those are compositions, not overrides,
 * and mixing them into 6,000 lines of desktop rules is how a mobile pass
 * turns into three hundred one-off media queries nobody can reason about.
 *
 * Kept here, the rules are legible as a design: three modes, one set of
 * tokens, and a small number of layout decisions applied consistently.
 *
 * ── The three modes ──────────────────────────────────────────────────────
 *
 *   PHONE    ≤ 899px wide, or any viewport under 500px TALL and under
 *            1100px wide. The second half is landscape phones: an iPhone
 *            14 Pro Max on its side is 932 × 430, which is wider than a
 *            small tablet and is emphatically not one. Height is what tells
 *            them apart, so height is what is asked.
 *
 *   TABLET   900 – 1099px, plus 1024px landscape tablets. Hybrid: the
 *            sidebar returns, the rail becomes a row of cards beneath the
 *            content, and a Book gets its two-page spread back.
 *
 *   DESKTOP  1100px and up. Untouched. Every rule in this file is inside a
 *            phone or tablet query, and nothing here can reach a desktop.
 *
 * `html.is-phone` is set by mobile.js from the SAME query, so JavaScript and
 * CSS can never disagree about which mode is running — a class set from one
 * breakpoint and a rule written against another is a bug that only appears
 * in the 40px where they differ.
 */

:root{
  /* ── Phone layout tokens ──────────────────────────────────────────────
     Every phone rule below is written in terms of these. A spacing change
     is one edit here, not a search across the file. */
  --m-pad:16px;              /* the page's side margin */
  --m-gap:14px;              /* between stacked cards */
  --m-radius:16px;
  --m-tap:44px;              /* the minimum comfortable touch target */
  --m-nav-h:60px;            /* the bottom navigation, excluding safe area */
  --m-top-h:52px;            /* the top bar */
  --m-safe-b:env(safe-area-inset-bottom,0px);
  --m-safe-t:env(safe-area-inset-top,0px);
  /* What content must clear at the bottom of every scrolling page. */
  --m-bottom-clear:calc(var(--m-nav-h) + var(--m-safe-b) + 18px);
  --m-sheet-max:min(92dvh,760px);
  --m-card:var(--surface);
}
@media (max-width:400px){
  :root{--m-pad:14px;--m-gap:12px}
}

/* ══════════════════════════════════════════════════════════════════════
   THE UPDATE PROMPT

   It had no phone rules at all, so it used the desktop geometry: pinned
   above `--composer-h`, a bar a phone does not have, and centred by
   `left:50%` at whatever width its content came to — measured at 360px it
   was a 253px box floating in the middle of the screen. Visible and
   tappable, but not obviously a thing that needed answering.

   A phone gets it as a bar above the navigation, the full width of the page.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .updater{left:12px;right:12px;transform:none;max-width:none;
    bottom:calc(var(--m-nav-h) + var(--m-safe-b) + 12px);
    padding:12px 14px;gap:10px;flex-wrap:wrap;
    background:var(--surface-2);border:1px solid var(--border-strong)}
  .updater .u-text{flex:1 1 100%;font-size:13.5px}
  .updater .btn{flex:1 1 0;min-height:42px;justify-content:center}
}

/* ══════════════════════════════════════════════════════════════════════
   THE PHONE PALETTE

   There is not one. A phone renders the desktop palette, unchanged.

   Three passes lifted it and a fourth put it back, and none of that was a
   colour problem: the app had been installed as a WebAPK from a browser
   other than Chrome, and that install rendered it wrongly. On a correct
   install #282431 on #141220 reads exactly as it does on a desktop. The
   only thing worth keeping from the whole detour is the note that a phone
   is not a different palette — so if a future change wants to fork one,
   the test below is what it has to argue with.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  :root{
    /* The bottom bar's material, read by the bar AND by the ring the centre
       button cuts in it. Opaque on purpose: a 92% ring painted over a 92%
       bar of the same colour composites to 99.4%, which is visibly lighter
       than the bar beside it — that was the halo round the button. Two
       identical opaque values cannot disagree. */
    --m-bar:#191622;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   PHONE SHELL
   The query below is THE phone query. It appears verbatim at the top of
   every phone block in this file and nowhere else, and mobile.js holds the
   same string. Do not paraphrase it.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){

  /* The sidebar does not become a drawer — it is not there at all.
   *
   * A drawer is a desktop sidebar with a hinge on it: every destination is
   * two taps away and the whole navigation lives off-screen behind a
   * hamburger. It also had a real defect — the scrim painted above the app
   * and swallowed every tap — which is a class of bug that cannot happen to
   * a bar that is simply part of the page.
   *
   * The bottom navigation replaces it outright. The scrim and the drawer
   * button go with it so nothing can toggle a body class that no longer
   * has a surface behind it. */
  .sidebar,.drawer-scrim,#drawer-btn{display:none!important}
  .shell{grid-template-columns:minmax(0,1fr)}
  :root{--sidebar-w:0px}

  /* The desktop composer is the assistant's home on a large screen. On a
   * phone the assistant is the centre of the bottom bar, so a second fixed
   * strip at the bottom is a duplicate that covers content — which is
   * precisely what it did: "Ask Life OS" sat on top of the Settings row. */
  .composer{display:none!important}

  .main{min-height:100dvh}
  .main-wrap{grid-template-columns:minmax(0,1fr);gap:var(--m-gap);
    padding:var(--m-pad) var(--m-pad) var(--m-bottom-clear)}
  .main-col{gap:18px}
  .rail{position:static;max-height:none;overflow:visible;
    display:flex;flex-direction:column;gap:var(--m-gap)}

  /* ── Top bar ────────────────────────────────────────────────────────
     52px, sticky, and it says where you are. The route name is here as
     well as in the bottom bar because the bottom bar's label is 10px and
     this one is the page's identity. */
  .mobile-bar{display:flex;align-items:center;gap:10px;position:sticky;top:0;z-index:100;
    background:rgba(20,18,30,.86);backdrop-filter:blur(14px);
    border-bottom:1px solid rgba(255,255,255,.085);
    height:calc(var(--m-top-h) + var(--m-safe-t));
    padding:0 10px 0 14px;padding-top:var(--m-safe-t)}
  .mobile-bar .logo-mark{flex:0 0 auto}
  /* ── Brand type is for the BRAND ──────────────────────────────────
     This is the surface's name — "Assistant", "Calendar" — not the wordmark,
     and Playfair made every page read like the title page of a book. The
     wordmark keeps its serif (`.logo-word`), and so do the Library covers and
     the Diary, where the editorial voice is the point. Application chrome
     speaks in the interface face. */
  .mobile-bar .m-title{font-family:inherit;
    font-size:17.5px;font-weight:600;letter-spacing:-.015em;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
  .mobile-bar .m-btn{flex:0 0 var(--m-tap);width:var(--m-tap);height:var(--m-tap);
    border-radius:12px;display:grid;place-items:center;color:var(--text-2)}
  .mobile-bar .m-btn:active{background:rgba(255,255,255,.07)}
  .mtop-spacer{margin-left:auto}

  h1{font-size:26px;letter-spacing:-.6px;line-height:1.15}
  .page-head .sub{font-size:13.5px}
  .page-actions{margin-left:0;width:100%;flex-wrap:wrap;flex-shrink:1}
}

/* ══════════════════════════════════════════════════════════════════════
   BOTTOM NAVIGATION
   Today | Calendar | ◎ | Projects | More
   ══════════════════════════════════════════════════════════════════════ */
.mnav{display:none}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* A direct child of #root, never of .shell.
   *
   * `.shell` was once `position:relative;z-index:1`, which made it a
   * stacking context and trapped everything inside it below a scrim that
   * was a sibling. The navigation is the one element that must never be
   * covered, so it does not live inside anything that could acquire a
   * z-index later. */
  .mnav{position:fixed;left:0;right:0;bottom:0;z-index:130;
    display:grid;grid-template-columns:repeat(5,minmax(0,1fr));align-items:end;
    height:calc(var(--m-nav-h) + var(--m-safe-b));
    padding-bottom:var(--m-safe-b);
    /* 1.002:1 against the canvas — the bar was not a bar, it was the
       background with icons floating in it. 1.174:1 reads as a band and
       still sits well below the task card, so it recedes. */
    /* No backdrop-filter: with an opaque bar there is nothing behind to
       blur, and it was the heaviest compositing step on the one element
       that must never render oddly. */
    background:var(--m-bar);
    border-top:1px solid rgba(255,255,255,.10)}

  .mnav-i{display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:3px;height:var(--m-nav-h);text-decoration:none;
    color:var(--muted);font-size:10.5px;font-weight:600;letter-spacing:.01em;
    background:none;border:0;position:relative;
    -webkit-tap-highlight-color:transparent;
    transition:color var(--d-fast) var(--e-out)}
  .mnav-i .mnav-ico{display:grid;place-items:center;width:24px;height:24px}
  .mnav-i svg{width:21px;height:21px}
  /* The current route, said three ways: colour, weight, and a bar at the
     top edge. Colour alone is not an answer for anyone who cannot see it. */
  .mnav-i[aria-current="page"]{color:var(--text)}
  .mnav-i[aria-current="page"]::before{content:'';position:absolute;top:0;
    left:50%;transform:translateX(-50%);width:22px;height:2.5px;border-radius:0 0 3px 3px;
    background:var(--accent)}
  .mnav-i:active{color:var(--text-2)}

  /* ── The centre action ───────────────────────────────────────────────
     Larger, lifted, and the only gradient in the bar. It is the assistant,
     and on a phone the assistant is the point. */
  .mnav-ai{position:relative;display:grid;place-items:center;
    width:56px;height:56px;margin:0 auto;justify-self:center;
    align-self:center;transform:translateY(-10px);
    border-radius:50%;border:0;padding:0;
    background:var(--brand);color:#fff;
    box-shadow:0 0 18px 2px rgba(var(--accent-rgb),.34);
    -webkit-tap-highlight-color:transparent;
    transition:transform var(--d-fast) var(--e-out),box-shadow var(--d-fast) var(--e-out)}
  .mnav-ai:active{transform:translateY(-10px) scale(.93)}
  .mnav-ai svg{width:26px;height:26px}
  .mnav-ai[aria-current="page"]{box-shadow:0 8px 26px -4px rgba(var(--accent-deep-rgb),.9),
    0 0 22px 4px rgba(var(--accent-rgb),.5)}
  /* The long-press affordance: a hint that fades in under the thumb rather
     than a label nobody reads. */
  .mnav-ai.is-holding{transform:translateY(-10px) scale(1.08)}

  /* A modal or sheet owns the screen while it is open. Leaving the bar
     visible under a bottom sheet puts two competing bottom edges on the
     same 60px, and on a phone the sheet is what the finger is aimed at. */
  body.modal-open .mnav,body.msheet-open .mnav{transform:translateY(110%);
    pointer-events:none;transition:transform var(--d-base) var(--e-out)}

  /* ── The virtual keyboard ────────────────────────────────────────────
     A fixed bottom bar and a software keyboard fight over the same edge.
     On Android the bar floats halfway up the screen; on iOS it sits on top
     of the field being typed into. mobile.js watches visualViewport and
     sets this class, and the bar simply leaves. */
  html.kb-open .mnav{display:none}
  html.kb-open .main-wrap{padding-bottom:24px}
}

@media (prefers-reduced-motion:reduce){
  .mnav-i,.mnav-ai,body.modal-open .mnav,body.msheet-open .mnav{transition:none}
}

/* ══════════════════════════════════════════════════════════════════════
   THE SHEET
   One primitive, used by More, Quick Add, a Book's contents, a Project's
   tasks, and the day list under Month. Building a second sheet for each
   of those is how an app ends up with five dismissal gestures.
   ══════════════════════════════════════════════════════════════════════ */
.msheet-scrim{position:fixed;inset:0;z-index:200;background:rgba(9,8,14,.66);
  opacity:0;transition:opacity var(--d-base) var(--e-out)}
.msheet-scrim.is-in{opacity:1}
.msheet{position:fixed;left:0;right:0;bottom:0;z-index:201;
  display:flex;flex-direction:column;
  max-height:var(--m-sheet-max);
  background:var(--surface);border-radius:22px 22px 0 0;
  box-shadow:0 -18px 50px rgba(0,0,0,.5);
  transform:translateY(101%);transition:transform var(--d-slow) var(--e-out);
  padding-bottom:var(--m-safe-b)}
.msheet.is-in{transform:none}
/* On a tablet or a desktop the same component is a centred dialog: a sheet
   glued to the bottom edge of a 1024px screen is a long way from the hand
   and from whatever opened it. */
@media (min-width:900px){
  .msheet{left:50%;bottom:auto;top:50%;right:auto;
    width:min(520px,calc(100vw - 64px));transform:translate(-50%,-46%) scale(.98);
    border-radius:20px;max-height:min(80vh,720px);opacity:0}
  .msheet.is-in{transform:translate(-50%,-50%);opacity:1}
}

.msheet-grab{flex:0 0 auto;width:38px;height:4px;border-radius:2px;
  background:var(--border-strong);margin:9px auto 2px}
@media (min-width:900px){.msheet-grab{display:none}}

.msheet-head{flex:0 0 auto;display:flex;align-items:center;gap:10px;
  padding:10px 16px 10px 18px;border-bottom:1px solid var(--hairline)}
.msheet-head h2{margin:0;font-size:16px;font-weight:600;letter-spacing:-.2px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.msheet-head .msheet-sub{font-size:12px;color:var(--muted);margin-left:2px;
  font-variant-numeric:tabular-nums}
.msheet-x{margin-left:auto;flex:0 0 var(--m-tap);width:var(--m-tap);height:var(--m-tap);
  display:grid;place-items:center;border-radius:11px;color:var(--text-2);font-size:19px}
.msheet-x:active{background:rgba(255,255,255,.07)}
.msheet-body{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
  overscroll-behavior:contain;padding:8px 0 14px}
.msheet-foot{flex:0 0 auto;padding:12px 16px calc(12px + var(--m-safe-b));
  border-top:1px solid var(--hairline);display:flex;gap:10px}
.msheet-foot .btn{flex:1;min-height:48px;justify-content:center}

/* A row inside a sheet. 52px, full-bleed press state, chevron on the right. */
.msheet-row{display:flex;align-items:center;gap:14px;width:100%;
  min-height:52px;padding:10px 18px;text-align:left;
  background:none;border:0;color:var(--text);font-size:15px;font-weight:500;
  text-decoration:none;-webkit-tap-highlight-color:transparent}
.msheet-row:active{background:rgba(255,255,255,.06)}
.msheet-row .msheet-ico{flex:0 0 26px;display:grid;place-items:center;color:var(--text-2)}
.msheet-row .msheet-ico svg{width:20px;height:20px}
.msheet-row .msheet-r{margin-left:auto;display:flex;align-items:center;gap:8px;
  color:var(--muted);font-size:12.5px}
.msheet-row .msheet-chev{color:var(--muted);opacity:.7}
.msheet-row[aria-current="page"]{color:var(--accent)}
.msheet-row[aria-current="page"] .msheet-ico{color:var(--accent)}
.msheet-row-desc{display:block;font-size:12px;font-weight:400;color:var(--muted);
  margin-top:2px;line-height:1.35}
.msheet-group{padding:14px 18px 6px;font-size:11px;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted)}
.msheet-sep{height:1px;background:var(--hairline);margin:8px 0}

@media (prefers-reduced-motion:reduce){
  .msheet,.msheet-scrim{transition:none}
  .msheet{transform:none;opacity:1}
  @media (min-width:900px){.msheet{transform:translate(-50%,-50%)}}
}

/* Shared bits a sheet's contents need. */
.msheet-pad{padding:6px 18px 12px}
.msheet-p{margin:0 0 10px;font-size:13.5px;line-height:1.55;color:var(--text-2)}
.msheet-list{margin:10px 0 0;padding-left:18px;font-size:13px;color:var(--text-2);line-height:1.7}
.msheet-label{display:block}

/* ══════════════════════════════════════════════════════════════════════
   TODAY, ON A PHONE
   Next -> the day in a line -> the assistant -> today's work -> habits
   -> everything later. In that order because that is the order the
   questions are asked in when somebody takes their phone out.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* The rail's only content is Habits, and on a phone Habits are a card in
     the flow and a sheet behind it — the same information, reached the way a
     phone reaches things. Not a removal: it is in More as well. */
  .rail{display:none}
  .main-wrap{grid-template-columns:minmax(0,1fr)}

  .m-sec-h{margin:0;font-size:11px;font-weight:700;letter-spacing:.11em;
    text-transform:uppercase;color:var(--muted)}

  /* ── Next ─────────────────────────────────────────────────────────── */
  .m-next{display:flex;flex-direction:column;gap:7px}
  .m-next-row{display:flex;align-items:center;gap:13px;width:100%;text-align:left;
    min-height:66px;padding:12px 14px;border-radius:var(--m-radius);
    background:linear-gradient(135deg,rgba(var(--accent-rgb),.17),rgba(var(--accent-rgb),.05));
    border:1px solid rgba(var(--accent-rgb),.24);color:var(--text)}
  .m-next-row:active{background:rgba(var(--accent-rgb),.24)}
  .m-next-time{flex:0 0 auto;font-size:17px;font-weight:700;letter-spacing:-.4px;
    font-variant-numeric:tabular-nums}
  .m-next-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
  .m-next-title{font-size:15px;font-weight:600;line-height:1.25;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .m-next-in{font-size:12.5px;color:var(--accent);font-weight:600}
  .m-next-chev{color:var(--muted);display:flex}
  .m-next.is-clear{flex-direction:row;align-items:center;gap:10px;
    padding:12px 14px;border-radius:var(--m-radius);
    background:var(--surface);border:1px solid var(--border)}
  .m-next-none{font-size:13.5px;color:var(--text-2)}
  .m-next-go{margin-left:auto;min-height:var(--m-tap);padding:0 12px;border-radius:10px;
    font-size:13px;font-weight:600;color:var(--accent)}

  /* ── The assistant's home state ───────────────────────────────────── */
  .m-ai{display:flex;flex-direction:column;gap:10px;align-items:stretch}
  .m-glance{margin:0;font-size:13.5px;color:var(--text-2);font-variant-numeric:tabular-nums}
  .ai-invite{display:flex;align-items:center;gap:15px;width:100%;text-align:left;
    padding:13px 16px 13px 10px;border-radius:20px;
    background:radial-gradient(120% 160% at 12% 50%,rgba(var(--accent-rgb),.20),rgba(var(--accent-rgb),.04));
    border:1px solid rgba(var(--accent-rgb),.24);color:var(--text);
    -webkit-tap-highlight-color:transparent}
  .ai-invite:active{background:rgba(var(--accent-rgb),.24)}
  .ai-invite-t{display:flex;flex-direction:column;gap:3px;min-width:0}
  .ai-invite-say{font-size:15.5px;font-weight:600;letter-spacing:-.2px}
  .ai-invite-sub{font-size:12px;color:var(--muted);line-height:1.35}

  /* ── The board ────────────────────────────────────────────────────── */
  .m-toolbar{display:flex;flex-direction:column;gap:10px;align-items:stretch}
  .m-toolbar .btn{min-height:46px;justify-content:center}
  .m-toolbar .filters{display:flex;gap:8px;flex-wrap:nowrap;overflow-x:auto;
    scrollbar-width:none;-webkit-overflow-scrolling:touch;
    margin-inline:calc(-1 * var(--m-pad));padding-inline:var(--m-pad)}
  .m-toolbar .filters::-webkit-scrollbar{display:none}
  .m-toolbar .chip{flex:0 0 auto}

  .m-buckets{display:flex;flex-direction:column;gap:var(--m-gap)}
  .m-buckets .bucket{min-width:0}

  /* ── Later ────────────────────────────────────────────────────────── */
  .m-later{display:flex;flex-direction:column;gap:8px}
  .m-later-b{background:var(--surface);border:1px solid var(--border);
    border-radius:var(--m-radius);overflow:hidden}
  .m-later-b > summary{display:flex;align-items:center;gap:10px;
    min-height:52px;padding:0 14px;cursor:pointer;list-style:none;
    font-size:14.5px;font-weight:600;-webkit-tap-highlight-color:transparent}
  .m-later-b > summary::-webkit-details-marker{display:none}
  .m-later-b > summary:active{background:rgba(255,255,255,.05)}
  .m-later-l{flex:1;min-width:0}
  .m-later-b .bucket-count{font-size:12px;font-weight:700;color:var(--muted);
    font-variant-numeric:tabular-nums;background:rgba(255,255,255,.06);
    border-radius:999px;padding:2px 9px}
  .m-later-chev{display:flex;color:var(--muted);
    transition:transform var(--d-fast) var(--e-out)}
  .m-later-b[open] > summary .m-later-chev{transform:rotate(90deg)}
  /* The bucket inside carries its own heading on the desktop; here the
     summary above IS the heading, and two would be a heading inside a
     heading. The count in the summary is the one syncBucketCounts finds,
     because it comes first in document order. */
  .m-later-b .bucket-head{display:none}
  .m-later-b .drop{padding:0 8px 10px}

  /* ── Habits ───────────────────────────────────────────────────────── */
  .m-habits{background:var(--surface);border:1px solid var(--border);
    border-radius:var(--m-radius);padding:4px 0 12px}
  .m-habits-h{display:flex;align-items:center;gap:10px;width:100%;
    min-height:var(--m-tap);padding:0 14px;text-align:left}
  .m-habits-h .m-sec-h{flex:1}
  .m-habits-n{font-size:13px;font-weight:700;color:var(--text-2);
    font-variant-numeric:tabular-nums}
  .m-habits-row{display:flex;gap:8px;overflow-x:auto;padding:4px 14px 2px;
    scrollbar-width:none;-webkit-overflow-scrolling:touch}
  .m-habits-row::-webkit-scrollbar{display:none}
  .m-hb{flex:0 0 auto;display:flex;align-items:center;gap:7px;min-height:40px;
    padding:0 13px 0 9px;border-radius:999px;background:rgba(255,255,255,.05);
    border:1px solid var(--border);font-size:13px;color:var(--text-2);
    -webkit-tap-highlight-color:transparent}
  .m-hb.is-done{background:rgba(0,217,163,.13);border-color:rgba(0,217,163,.4);color:var(--text)}
  .m-habits-empty{margin:2px 14px 0;font-size:13px;color:var(--muted)}
}

/* The habits sheet. Outside the phone query on purpose: the sheet component
   is used on a tablet too, where it is a centred dialog. */
.m-hrow{display:flex;align-items:center;gap:8px;padding-right:14px}
.m-hrow-main{flex:1;display:flex;align-items:center;gap:14px;min-height:54px;
  padding:8px 0 8px 18px;text-align:left;color:var(--text);
  -webkit-tap-highlight-color:transparent}
.m-hrow-main:active{background:rgba(255,255,255,.06)}
.m-hrow-tick{flex:0 0 24px;width:24px;height:24px;border-radius:50%;
  display:grid;place-items:center;border:1.5px solid var(--border-strong);
  font-size:13px;color:#0E0B16}
.m-hrow.is-done .m-hrow-tick{background:var(--ok);border-color:var(--ok)}
.m-hrow-t{min-width:0}

/* Quick add. The field is first and focused: capture must never wait on a
   decision about which KIND of thing is being captured. */
.qa-form{display:flex;gap:9px;align-items:stretch}
.qa-form .m-input{flex:1;min-width:0;min-height:48px}
.qa-form .btn{min-height:48px;padding:0 18px}
.qa-hint{margin:9px 0 0;font-size:12px;color:var(--muted);line-height:1.45}

/* ══════════════════════════════════════════════════════════════════════
   THE ASSISTANT
   Not phone-only: the route resolves at every width so a refresh or a
   shared link lands somewhere real. It is COMPOSED for a phone, which is
   where it is meant to be used.
   ══════════════════════════════════════════════════════════════════════ */
.asst{display:flex;flex-direction:column;gap:16px;min-height:60vh;
  max-width:620px;margin:0 auto;width:100%}

/* The label. It is at the top, in the flow, and it does not go away — an
   assistant that is a prototype has to say so every time it is used, not
   once in a tooltip somebody dismissed a week ago. */
.asst-note{display:flex;gap:10px;align-items:flex-start;
  padding:11px 14px;border-radius:12px;font-size:12.5px;line-height:1.5;
  background:rgba(255,196,77,.08);border:1px solid rgba(255,196,77,.24);
  color:var(--text-2)}
.asst-note b{color:var(--warn)}
.asst-dot{flex:0 0 8px;width:8px;height:8px;border-radius:50%;
  background:var(--warn);margin-top:6px}

/* ── The transcript ─────────────────────────────────────────────────────
   Above the orb, and it only takes space once there are words in it (§10).
   An empty box reserved for text that has not arrived reads as a failure. */
.asst-script{min-height:0;max-height:34vh;overflow-y:auto;-webkit-overflow-scrolling:touch}
.asst-line{margin:0;font-size:17px;line-height:1.45;letter-spacing:-.2px;color:var(--text)}
.asst-src{margin:0;font-size:11.5px;color:var(--muted);line-height:1.45;
  padding:7px 11px;border-radius:9px;background:rgba(255,255,255,.045)}
.asst-src[hidden]{display:none}

/* ── The stage ─────────────────────────────────────────────────────────
   The orb, and the one line saying what it is doing. Once a transcript
   exists the whole stage moves DOWN, opening the space above it — one
   composited transform, and it reverses cleanly. */
.asst-stage{display:flex;flex-direction:column;align-items:center;gap:10px;
  padding:10px 0 2px;transition:transform var(--d-slower) var(--e-out)}
.asst.has-script .asst-stage{transform:translateY(10px)}
.asst[data-state="proposal"] .asst-stage{transform:scale(.62);
  transform-origin:top center;margin-bottom:-58px;opacity:.75}
.asst-say{margin:0;font-size:16px;font-weight:600;letter-spacing:-.2px;text-align:center}
.asst-sub{margin:0;font-size:12.5px;color:var(--muted);text-align:center;min-height:17px}
.asst[data-state="listening"] .asst-say,
.asst[data-state="paused"] .asst-say{color:var(--accent)}

.asst-actions{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
.asst-actions .btn{min-height:48px}
.asst-big{padding:0 26px;font-size:15px}

/* The composer sits in the page under the buttons, not in a sheet over them.
   Its own surface so it reads as a distinct place to write, without becoming
   a second screen. */
/* The on-device offer. One line, the same restraint as .asst-note, with the
   action reading as a link rather than competing with Speak. */
.asst-offer{margin:0 0 14px;font-size:13px;line-height:1.55;color:var(--text-2);
  padding:11px 13px;border-radius:var(--r-md);background:var(--surface-2);
  border:1px solid var(--line)}
.asst-offer-go{display:inline;padding:0;border:0;background:none;font:inherit;
  color:var(--brand);text-decoration:underline;text-underline-offset:2px;
  min-height:44px;cursor:pointer}
.asst-offer-go:disabled{color:var(--text-3);text-decoration:none;cursor:default}

.asst-compose{margin-top:16px;display:flex;flex-direction:column;gap:12px;
  padding:14px;border-radius:var(--r-lg);background:var(--surface-2);
  border:1px solid var(--line)}
.asst-compose .asst-ta{min-height:92px}
.asst-compose-acts{display:flex;gap:10px;align-items:center;
  justify-content:flex-end}
.asst-compose-acts .btn{min-height:48px}
/* Send is the commitment, so it is the one that grows. */
.asst-compose-acts .btn-primary{flex:1;max-width:220px;justify-content:center}

/* ── The orb ───────────────────────────────────────────────────────────
   The canvas draws the body and everything the voice does to it. The mark
   is a DOM element on top, so it stays crisp and is never re-rasterised. */
/* The diagnostics fallback box, for a browser that refuses the clipboard.
   Monospace and selectable — its whole job is to be copied by hand. */
/* The development panel is collapsed by default and must read as a testing
   drawer, not as part of the product. */
.asst-dev-sum{cursor:pointer;list-style:none;display:flex;align-items:center;gap:8px;
  font-size:11px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;
  color:var(--muted);padding:10px 2px}
.asst-dev-sum::-webkit-details-marker{display:none}
.asst-dev-sum::before{content:'▸';font-size:10px;transition:transform var(--t-hover)}
.asst-dev[open] .asst-dev-sum::before{transform:rotate(90deg)}
/* A count when a trace is waiting, so it is obvious there is data to copy. */
.asst-dev-sum[data-count]:not([data-count=""])::after{content:attr(data-count);
  font-size:10px;font-weight:700;letter-spacing:0;color:#0F0A1E;
  background:var(--accent-pale);border-radius:999px;padding:1px 7px}
.asst-dev-body{padding-bottom:6px}
.chip-lead{background:var(--accent-soft);color:var(--text);font-weight:600}
.chip-ready{box-shadow:0 0 0 1px var(--accent-deep)}
/* The development amplitude meter. Not a user feature — it exists so the
   signal driving the waveform can be seen rather than guessed at. */
/* The listening-visual lab. Dense on purpose — it is an instrument, and
   every row costs screen the orb should be using. */
.asst-lab{display:grid;gap:6px;margin:8px 0 10px}
.asst-lab-row{display:grid;grid-template-columns:1fr 1.35fr auto;align-items:center;gap:8px;
  font-size:11.5px;color:var(--text-2)}
.asst-lab-row b{font-weight:600;color:var(--text);font-variant-numeric:tabular-nums;
  min-width:34px;text-align:right}
.asst-lab-row input[type=range]{width:100%;accent-color:var(--accent)}
.asst-lab-row select,.asst-lab-row input[type=color]{background:var(--surface-2);
  border:1px solid var(--border);border-radius:8px;color:var(--text);
  font-family:inherit;font-size:11.5px;padding:3px 6px}
.asst-lab-row input[type=color]{padding:1px;height:26px}
#asst-presets{max-height:120px;overflow:auto}
.asst-meter{display:block;height:6px;border-radius:999px;background:var(--surface-2);
  overflow:hidden;margin:2px 0 10px}
.asst-meter-fill{display:block;height:100%;width:0%;border-radius:999px;
  background:var(--accent);transition:width 60ms linear}
.asst-trace-box{width:100%;min-height:180px;margin-top:10px;padding:10px;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.45;
  color:var(--text);background:var(--surface-2);border:1px solid var(--border);
  border-radius:10px;white-space:pre;overflow:auto;-webkit-user-select:text;user-select:text}
.orb{position:relative;display:grid;place-items:center;flex:0 0 auto}
.orb-lg{width:min(280px,72vw);height:min(280px,72vw)}
.orb-md{width:88px;height:88px}
.orb-canvas{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
/* The big orb's canvas is deliberately LARGER than the orb, so the waves have
   somewhere to travel. They used to die against the canvas edge, which read as
   a square box appearing around a round thing whenever you spoke up. The orb
   itself does not grow: assistant-orb.js takes its radius from this element's
   parent, and treats the extra as bleed. */
/* Sized explicitly and centred, rather than by a negative inset: `width:auto`
   on a canvas means its INTRINSIC size, so the inset was ignored and the
   element fell back to 300x150.
   CAPPED AT THE VIEWPORT. 162% of a 248px orb is 401px, which is wider than a
   375px phone — and an absolutely positioned element that overhangs still
   lengthens the document, so the Assistant became the one page you could
   scroll sideways. The orb does not change size: the drawn radius comes from
   this element's PARENT, so capping the canvas only trims how far the ribbon
   may travel, and it fades long before the edge either way. */
.orb-lg .orb-canvas{width:min(162%,92vw);height:min(162%,92vw);
  left:50%;top:50%;right:auto;bottom:auto;transform:translate(-50%,-50%)}
.orb-mark{position:relative;display:grid;place-items:center;
  filter:drop-shadow(0 2px 6px rgba(0,0,0,.35))}
/* The mark rides on top of the body, so it is white rather than the brand
   gradient — a purple lotus on a purple ball is a lotus nobody can see. */
.orb-mark svg path{fill:#fff;fill-opacity:.94}
.orb-mark svg path:first-of-type,.orb-mark svg path:nth-of-type(2){fill-opacity:.72}

/* ── The proposal ──────────────────────────────────────────────────────
   Once there is something to agree to, it is the page. */
.asst-review{display:flex;flex-direction:column;gap:12px}
.asst-h{margin:0;font-size:13px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted)}
.asst-reply{margin:0;font-size:15.5px;line-height:1.5;color:var(--text)}
/* Same fixed subset as the desktop panel. See assistant-prose.js. */
.asst-reply > *{margin:0}
.asst-reply > * + *{margin-top:9px}
.asst-reply ul,.asst-reply ol{margin:0;padding-left:20px;
  display:flex;flex-direction:column;gap:5px}
.asst-reply li{line-height:1.5}
.asst-reply strong{font-weight:600}
.asst-reply code{font-family:var(--font-mono,ui-monospace,monospace);font-size:.92em}
.asst-empty{margin:0;font-size:13px;color:var(--muted)}

.ap{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--m-radius);padding:13px 14px 12px;
  display:flex;flex-direction:column;gap:7px;
  transition:opacity var(--d-fast) var(--e-out)}
.ap.is-off{opacity:.44}
.ap.is-important{border-color:rgba(255,196,77,.34)}
.ap-head{display:flex;align-items:center;gap:9px}
.ap-kind{font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--accent);background:var(--accent-soft);border-radius:6px;padding:3px 8px}
.ap.is-important .ap-kind{color:var(--warn);background:rgba(255,196,77,.13)}
.ap-flag{font-size:11px;color:var(--warn)}
.ap-on{margin-left:auto;display:grid;place-items:center;
  width:var(--m-tap);height:var(--m-tap);margin-right:-10px;margin-top:-8px;margin-bottom:-8px}
.ap-on input{width:22px;height:22px;accent-color:var(--accent)}
.ap-title{margin:0;font-size:16px;font-weight:600;line-height:1.3;letter-spacing:-.2px}
.ap-sum{margin:0;font-size:13px;color:var(--text-2)}
.ap-ctx{margin:0;font-size:12.5px;color:var(--ok)}
.ap-items{margin:2px 0 0;padding:0;list-style:none;display:flex;flex-direction:column}
.ap-item{display:flex;align-items:center;gap:11px;min-height:var(--m-tap);
  font-size:14.5px;cursor:pointer}
.ap-item input{width:20px;height:20px;accent-color:var(--accent)}
.ap-fields{display:flex;flex-wrap:wrap;gap:7px;margin-top:3px}
.ap-field{display:inline-flex;align-items:center;gap:7px;min-height:38px;
  padding:0 11px;border-radius:10px;background:rgba(255,255,255,.05);
  border:1px solid var(--border);color:var(--text);font-size:13px;
  position:relative}
/* 44px of finger over a 38px control — the row would grow to 44 and the card
   would become a stack of buttons. The paint stays small; the target does not. */
.ap-field::after{content:'';position:absolute;top:50%;left:0;right:0;
  height:var(--m-tap);transform:translateY(-50%)}
.ap-field:active{background:rgba(255,255,255,.1)}
.ap-flabel{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.06em}
.ap-fvalue{font-weight:600}
.ap-field svg{color:var(--muted)}

/* An interpretation made on the user's behalf, said out loud on the card.
   This is what lets a medium-confidence reading be a proposal instead of a
   question — quiet enough not to alarm, present enough to be corrected. */
.ap-assume{margin:1px 0 0;padding:0 0 0 14px;list-style:none;
  display:flex;flex-direction:column;gap:3px}
.ap-assume li{position:relative;font-size:12.5px;color:var(--muted);line-height:1.45}
.ap-assume li::before{content:'';position:absolute;left:-14px;top:.62em;
  width:5px;height:5px;border-radius:50%;background:var(--muted);opacity:.55}

/* Things that are true and unwelcome: a clash, a read-only calendar. Warm,
   never red — a warning is not an error, and the action can still be taken. */
.ap-warn{margin:1px 0 0;padding:0;list-style:none;display:flex;flex-direction:column;gap:4px}
.ap-warn li{display:flex;align-items:flex-start;gap:7px;font-size:12.5px;
  color:var(--warn);line-height:1.45}
.ap-warn svg{flex-shrink:0;margin-top:1px}
.ap-gone{color:var(--muted)}
/* The sentence under a card that cannot run. Why, not just no. */
.ap-gone-why{margin:0;font-size:12.5px;line-height:1.45;color:var(--muted)}

/* Where an answer came from. One quiet line of chips, not a citation report. */
.ap-src{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}
.ap-src-l{font-size:10.5px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted)}
.ap-src-i{font-size:12px;color:var(--text-2);background:rgba(255,255,255,.05);
  border:1px solid var(--border);border-radius:8px;padding:4px 9px;
  max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  transition:background var(--t-hover),color var(--t-hover)}
.ap-src-i:hover{background:rgba(255,255,255,.09);color:var(--text)}
.ap-src-more{font-size:12px;color:var(--muted)}

/* The one genuinely necessary question. Shown instead of a guess, never as
   the default response to ordinary uncertainty. */
.ap-ask{background:var(--surface);border:1px solid rgba(255,196,77,.34);
  border-radius:var(--m-radius);padding:13px 14px;display:flex;
  flex-direction:column;gap:10px}
.ap-ask-q{margin:0;font-size:15px;line-height:1.45}
.ap-ask-opts{display:flex;flex-wrap:wrap;gap:8px}
/* Two lines: the name, and the one fact that tells it from the other one.
   Stacked rather than run together, because the whole job of this control is
   letting somebody tell two things with the same name apart at a glance. */
.ap-ask-o{min-height:44px;display:flex;flex-direction:column;align-items:flex-start;
  gap:2px;text-align:left;padding-top:7px;padding-bottom:7px}
.ap-ask-l{font-size:14px;line-height:1.3}
.ap-ask-d{font-size:12px;line-height:1.3;color:var(--muted)}

/* What actually happened. Three of four succeeding is three things that
   happened, and it is never reported as a failure. */
.ap-done{background:var(--surface);border:1px solid var(--border);
  border-radius:var(--m-radius);padding:13px 14px;display:flex;
  flex-direction:column;gap:9px}
.ap-done.has-fail{border-color:rgba(255,196,77,.34)}
.ap-done-h{margin:0;font-size:15.5px;font-weight:600}
.ap-done-l{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:6px}
.ap-done-l li{display:flex;align-items:flex-start;gap:8px;font-size:13.5px;line-height:1.45}
.ap-done-l li.ok{color:var(--text-2)}
.ap-done-l li.ok svg{color:var(--ok)}
.ap-done-l li.bad{color:var(--warn)}
.ap-done-l svg{flex-shrink:0;margin-top:2px}

/* Confirm. It counts, and the number is derived from the list above it —
   see changeCount() for why those two can never disagree. */
.asst-confirm{position:sticky;bottom:calc(var(--m-nav-h) + var(--m-safe-b) + 8px);
  display:flex;gap:10px;padding:10px 0 2px;z-index:5}
.asst-confirm .btn{flex:1;min-height:52px;justify-content:center;font-size:15px}
.asst-confirm .btn-primary{flex:2}
@media (min-width:900px){.asst-confirm{bottom:14px}}

.asst-ta{width:100%;min-height:110px;resize:vertical;line-height:1.5}
.asst-demos{margin-top:12px;display:flex;flex-wrap:wrap;gap:7px;align-items:center}
.asst-demos-h{width:100%;font-size:11px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted)}

/* ── Development panel ─────────────────────────────────────────────────
   Marked as development, at the bottom, and it disappears with DEV_PREVIEW.
   One of the three listening styles will be chosen and the other two
   deleted, so this is not a preference and does not belong in Settings. */
.asst-dev{margin-top:8px;padding:14px;border-radius:var(--m-radius);
  border:1px dashed var(--border-strong);background:rgba(255,255,255,.02)}
.asst-dev h2{margin:0 0 4px;font-size:11px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--muted)}
.asst-dev-p{margin:0 0 10px;font-size:12px;color:var(--muted);line-height:1.5}
.asst-dev-row{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:8px}
.asst-dev-hint{margin:0 0 10px;font-size:12px;color:var(--text-2)}

@media (prefers-reduced-motion:reduce){
  .asst-stage{transition:none}
}

/* ══════════════════════════════════════════════════════════════════════
   CALENDAR
   Agenda | Day | 3 day | Month. Not a shrunken desktop calendar: Plan
   week is not offered here at all, because seven draggable columns on a
   390px screen is seven columns nobody can read or hit.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .cal-head,.cal-body{margin-left:0;max-width:none}
  .cal-head-row{flex-direction:column;align-items:stretch;gap:11px}
  .cal-head-side{justify-content:space-between}
  /* Four modes across 390px. The pill track fills the row and each tab is a
     quarter of it — equal, tappable, and never a horizontal scroller, which
     would hide the mode you are not currently in. */
  .cal-modes{flex:1;width:100%}
  .cal-modes [role="tab"]{min-height:42px;padding:0 4px;font-size:12.5px}
  .cal-period-label{flex:1;min-width:0}
  .cal-nav,.cal-today{min-height:var(--m-tap)}
  .cal-body{grid-template-columns:minmax(0,1fr)}
  .cal-rail{position:static;overflow:visible}
  .cal-rail-in{width:auto;margin-left:0;margin-top:16px;opacity:1}

  /* ── Agenda ───────────────────────────────────────────────────────────
     The default, and the view a phone is picked up for. Time on the left,
     what it is on the right, days as headings. */
  .cal-agenda{max-width:none}
  .cal-body .ag-day{grid-template-columns:minmax(0,1fr);gap:6px;padding:4px 0 12px}
  .ag-item{min-height:52px}

  /* ── Day and 3 day ────────────────────────────────────────────────────
     The same grid Plan week is built from, with one or three columns. The
     hour rows are taller than the desktop's 46px: an hour that is 46px tall
     makes a half-hour 23px, and nothing at 23px can be hit with a thumb. */
  .cal-plan{overflow-x:visible}
  .cal-plan.is-day .pl-grid,.cal-plan.is-three .pl-grid{min-width:0;gap:4px}
  .cal-plan.is-day .pl-grid{grid-template-columns:44px minmax(0,1fr)}
  .cal-plan.is-three .pl-grid{grid-template-columns:38px repeat(3,minmax(0,1fr))}
  .cal-plan.is-day .pl-canvas,.cal-plan.is-three .pl-canvas{
    min-height:calc(var(--pl-hours) * 68px)}
  .cal-plan.is-day .pl-ev,.cal-plan.is-day .pl-block,
  .cal-plan.is-three .pl-ev,.cal-plan.is-three .pl-block{font-size:12px;padding:5px 8px}
  .cal-plan.is-day .pl-ev b,.cal-plan.is-three .pl-ev b{font-size:11px}
  .cal-plan.is-day .pl-hour,.cal-plan.is-three .pl-hour{font-size:10.5px}
  .cal-plan.is-day .pl-num{font-size:17px}
  /* A weekend is not a lesser day on a phone — the narrowing that keeps a
     seven-column week readable has nothing to narrow here. */
  .cal-plan.is-day .pl-day.is-weekend .pl-day-head,
  .cal-plan.is-three .pl-day.is-weekend .pl-day-head{opacity:1}
  .cal-plan.is-day .pl-day.is-weekend .pl-canvas,
  .cal-plan.is-three .pl-day.is-weekend .pl-canvas{background:var(--surface)}
  /* Plan week is not offered on a phone, but a tablet rotating into a phone
     width mid-render must not leave a 820px grid behind. */
  .cal-plan.is-week{overflow-x:auto}

  /* ── Month ────────────────────────────────────────────────────────────
     Dates, density, selection and today. Tap a day and it opens beneath. */
  .cm-grid{grid-auto-rows:minmax(52px,auto);gap:3px}
  .cm-cell-m{display:flex;flex-direction:column;align-items:center;justify-content:flex-start;
    gap:4px;padding:6px 2px;min-height:52px}
  .cm-cell-m .cm-date{font-size:14px;font-weight:600;padding:2px 6px}
  .cm-dots{display:flex;align-items:center;justify-content:center;gap:3px;flex-wrap:wrap;
    min-height:7px;max-width:100%}
  .cm-dots i{width:5px;height:5px;border-radius:50%;display:block;flex:0 0 auto}
  .cm-dots .cm-dot-warn{background:var(--warn)}
  .cm-dots-n{font-size:9px;font-weight:700;color:var(--muted);
    font-variant-numeric:tabular-nums;margin-left:1px}
  .cm-dow{font-size:10px}
  /* The selected day's panel is the rail, stacked underneath. Same content
     the desktop shows beside the grid — moved, not reduced. */
  .cal-rail-in .cs-card,.cal-rail-in .rail-card{width:auto}
}

/* ══════════════════════════════════════════════════════════════════════
   TASKS ON A PHONE
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* The row already reflows (title on its own line, controls beneath) from
     the touch pass. What it needs here is somewhere for the swipe to happen. */
  .task{position:relative;overflow:hidden}
  .task > .t-row,.task > .t-steps{position:relative;z-index:1;background:inherit}
}

/* The layer behind a swiping row. It says what will happen BEFORE release —
   a swipe whose outcome is only revealed by doing it is a gamble, and people
   stop using it after the first time it does the wrong thing. */
.swipe-back{position:absolute;inset:0;display:flex;align-items:center;
  padding:0 18px;font-size:13px;font-weight:700;letter-spacing:.02em;
  color:var(--text);pointer-events:none;border-radius:inherit;opacity:.55}
.swipe-back.is-right{justify-content:flex-start;background:rgba(0,217,163,.22)}
.swipe-back.is-left{justify-content:flex-end;background:rgba(var(--accent-rgb),.22)}
.swipe-back.is-armed{opacity:1}
.swipe-back.is-right.is-armed{background:rgba(0,217,163,.34)}
.swipe-back.is-left.is-armed{background:rgba(var(--accent-rgb),.36)}

/* ══════════════════════════════════════════════════════════════════════
   DIALOGS BECOME SHEETS
   Every modal in the app, at the phone breakpoint — not just the four
   that were listed by name last time. A dialog centred on a 390px screen
   is a dialog with 20px of margin pretending to be a desktop.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .modal,.modal-narrow,.modal-event,.modal-reminder,.modal-schedule,
  .modal-detail,.modal-picker,.modal-choice,.modal.ev-modal{
    left:0;right:0;bottom:0;top:auto;transform:none;
    width:100%;max-width:100%;max-height:min(94dvh,100dvh - 24px);
    border-radius:22px 22px 0 0}
  /* The head stays put and the body scrolls, so the title of what you are
     editing does not scroll away from the fields that edit it. */
  .modal{display:flex;flex-direction:column}
  .m-head{flex:0 0 auto}
  .m-body{flex:1 1 auto;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
    padding-bottom:16px}
  /* The footer is pinned, and it rides ABOVE the keyboard rather than under
     it. --kb-h is published by mobile.js from visualViewport; it is 0 when
     no keyboard is up, so the safe-area padding is what applies then. */
  .m-foot{flex:0 0 auto;position:sticky;bottom:0;z-index:2;
    background:var(--surface);border-top:1px solid var(--hairline);
    padding-bottom:calc(14px + var(--m-safe-b))}
  .m-foot .btn{min-height:46px}
  .m-foot .btn-primary{flex:1;justify-content:center}
  .m-grid{grid-template-columns:1fr;gap:12px}
  .m-title{font-size:19px}

  /* §17 The disclosure. Everything the desktop form has is still in the
     sheet; the second half of it is one tap down rather than competing with
     the title for the first screenful. */
  .m-more{border-top:1px solid var(--hairline);margin-top:4px;padding-top:4px}
  .m-more > summary{display:flex;align-items:center;gap:8px;list-style:none;
    min-height:var(--m-tap);font-size:13.5px;font-weight:600;color:var(--text-2);
    cursor:pointer;-webkit-tap-highlight-color:transparent}
  .m-more > summary::-webkit-details-marker{display:none}
  .m-more-chev{margin-left:auto;width:8px;height:8px;border-right:1.6px solid currentColor;
    border-bottom:1.6px solid currentColor;transform:rotate(45deg) translate(-2px,-2px);
    transition:transform var(--d-fast) var(--e-out)}
  .m-more[open] .m-more-chev{transform:rotate(225deg) translate(-2px,-2px)}
  .m-more > .m-grid{padding-bottom:8px}

  /* Anything that was a hover-only affordance inside a dialog is permanent. */
  .m-steps .ts-row .ts-x{opacity:1}
}

/* Outside the phone query: the disclosure exists at every width, because the
   editor is one component and a control that only exists on one device is a
   control the other device's users get told about and cannot find. */
.m-more{border-top:1px solid var(--hairline);margin-top:2px}
.m-more > summary{display:flex;align-items:center;gap:8px;list-style:none;
  min-height:40px;font-size:13px;font-weight:600;color:var(--text-2);cursor:pointer}
.m-more > summary::-webkit-details-marker{display:none}
.m-more-chev{margin-left:auto;width:7px;height:7px;border-right:1.6px solid currentColor;
  border-bottom:1.6px solid currentColor;transform:rotate(45deg) translate(-2px,-2px);
  transition:transform var(--d-fast) var(--e-out)}
.m-more[open] .m-more-chev{transform:rotate(225deg) translate(-2px,-2px)}

/* ══════════════════════════════════════════════════════════════════════
   ONE DROPDOWN GRAMMAR (§38)
   On a phone every menu is a sheet. The shared dropdown gets there
   through mobile.js; the date and time pickers get there through this,
   because they are anchored panels and an anchored panel near the bottom
   of a bottom sheet has nowhere left to go.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* Fixed, not absolute: it leaves the dialog's coordinate space entirely,
     so the inline left/top that anchor() wrote no longer means anything and
     the panel is simply attached to the bottom of the screen.

     Scoped away from the three creation flows, which are centred dialogs on
     a phone now (§9) — inside a card that is not touching the bottom edge,
     a picker glued to the bottom edge belongs to neither. */
  .modal:not(.ev-modal):not(.modal-reminder):not(.modal-schedule) .cf-pop:not([hidden]){
    position:fixed;inset:auto 0 0 0;
    left:0!important;top:auto!important;width:100%;z-index:210;
    max-height:min(72dvh,560px);overflow-y:auto;-webkit-overflow-scrolling:touch;
    background:var(--surface);border-top:1px solid var(--border);
    border-radius:20px 20px 0 0;box-shadow:0 -16px 44px rgba(0,0,0,.5);
    padding-bottom:calc(8px + var(--m-safe-b))}
  .modal:not(.ev-modal):not(.modal-reminder):not(.modal-schedule) .cf-pop .cf-menu,
  .modal:not(.ev-modal):not(.modal-reminder):not(.modal-schedule) .cf-pop .dp,
  .modal:not(.ev-modal):not(.modal-reminder):not(.modal-schedule) .cf-pop .tp{
    max-height:none;width:auto}
  .cf-pop .dp-day,.cf-pop .tp-opt{min-height:var(--m-tap)}
  .cf-ctl{height:auto;min-height:var(--m-tap);padding:0 12px}
  .cf-menu-btn{min-width:0}
}

/* The dropdown's own rows inside a sheet. */
.cf-sheet-opt .cf-menu-tick{width:9px;height:5px;
  border-left:1.8px solid var(--accent);border-bottom:1.8px solid var(--accent);
  transform:rotate(-45deg) translate(1px,-2px)}
.cf-sheet-opt[aria-current="page"] .msheet-label{color:var(--accent);font-weight:600}
.cf-sheet-opt .cf-menu-mark{width:9px;height:9px;border-radius:50%;flex:0 0 auto;
  margin-left:18px}

/* ══════════════════════════════════════════════════════════════════════
   THE BOOK ON A PHONE (§27, §28, §30, §31, §32)
   One readable page. The tab strip becomes one control that says where
   you are; the Project rail becomes a sheet; the formatting toolbar
   moves to the keyboard.
   ══════════════════════════════════════════════════════════════════════ */
.bk-mbar{display:none}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .bk-mbar{display:flex;align-items:center;gap:8px;width:100%;
    padding:0 0 10px;border-bottom:1px dashed var(--border);margin-bottom:12px}
  .bk-mbar-x{flex:0 0 var(--m-tap);width:var(--m-tap);height:var(--m-tap);
    display:grid;place-items:center;border-radius:11px;color:var(--text-2)}
  .bk-mbar-x svg{width:20px;height:20px}
  .bk-mbar-x:active{background:rgba(255,255,255,.07)}
  .bk-mbar-mid{flex:1;min-width:0;display:flex;align-items:baseline;gap:9px;
    min-height:var(--m-tap);padding:0 8px;border-radius:11px;text-align:left}
  .bk-mbar-mid:active{background:rgba(255,255,255,.06)}
  .bk-mbar-sec{font-family:'Playfair Display',Georgia,serif;font-size:16px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
  .bk-mbar-n{flex:0 0 auto;font-size:12px;color:var(--muted);
    font-variant-numeric:tabular-nums}
  .bk-mbar-tasks{flex:0 0 auto;display:flex;align-items:center;gap:6px;
    min-height:var(--m-tap);padding:0 12px;border-radius:11px;
    background:var(--surface-2);border:1px solid var(--border);
    font-size:12.5px;font-weight:600;color:var(--text-2)}
  .bk-mbar-tasks b{font-variant-numeric:tabular-nums;color:var(--accent)}

  /* The tab strip and the bookmark row are IN the contents sheet now. They
     are not gone — they are one tap away, and the page has its width back. */
  .bk-tabs,.bk-marks{display:none}

  /* The rail lives in a sheet on a phone; hiding the aside also collapses
     the column it sat in, or the book keeps a 320px gap beside it. */
  .bk-layout.has-rail{grid-template-columns:minmax(0,1fr)}
  .bk-rail{display:none}
  /* …except while it is inside one, where it is the sheet's contents. */
  .msheet .bk-rail{display:block;position:static;max-height:none;width:auto;
    background:none;border:0;padding:0 16px 8px}
  .msheet .bk-rail-in{width:auto}

  /* One page, always. Text at reading size, phone margins, and the arrows
     over the outer edges rather than beside the page — see app.css, which
     already does this below 480px; this brings it to every phone width. */
  .bk-book.bk-spread:not(.dia-book){aspect-ratio:auto;min-height:60dvh;
    grid-template-columns:minmax(0,1fr);max-width:none}
  .bk-page{padding:20px 16px 14px 40px;font-size:15px}
  .bk-page-right{padding:20px 40px 14px 16px}
  .bk-page::before{left:32px}
  .bk-page-right::before{left:auto;right:32px}
  .bk-stage{position:relative;gap:0}
  .bk-arrow{position:absolute;top:50%;transform:translateY(-50%);z-index:3;
    width:var(--m-tap);height:var(--m-tap);
    background:rgba(20,18,32,.72);backdrop-filter:blur(6px);
    border-color:rgba(255,255,255,.1)}
  .bk-arrow:disabled{opacity:.22}
  #bk-prev{left:2px}
  #bk-next{right:2px}

  /* ── §32 The formatting toolbar goes to the keyboard ──────────────────
     A toolbar at the top of the page is several hundred pixels from a caret
     at the bottom of it, and once the keyboard is up it may not be on screen
     at all. --kb-h is the measured keyboard height, published by mobile.js.
     When no keyboard is up the toolbar stays where it was. */
  html.kb-open .bk-toolbar{position:fixed;left:0;right:0;bottom:var(--kb-h,0px);
    z-index:140;margin:0;border-radius:0;
    background:rgba(23,21,31,.97);backdrop-filter:blur(14px);
    border-top:1px solid var(--border);
    padding:6px 8px calc(6px + env(safe-area-inset-bottom,0px));
    display:flex;flex-wrap:nowrap;overflow-x:auto;gap:4px;
    scrollbar-width:none}
  html.kb-open .bk-toolbar::-webkit-scrollbar{display:none}
  html.kb-open .bk-toolbar .bk-tb{flex:0 0 auto;min-width:42px;min-height:42px}
  html.kb-open .bk-toolbar .bk-tb-style{flex:0 0 auto;min-height:42px}
  /* Room under the page for it, so the last line is never behind the bar. */
  html.kb-open .bk-page{padding-bottom:64px}

  .bk-toolbar{width:100%;justify-content:flex-start;overflow-x:auto;
    flex-wrap:nowrap;scrollbar-width:none}
  .bk-toolbar::-webkit-scrollbar{display:none}
  .bk-tb{min-width:40px;min-height:40px}
  .bk-search{position:fixed;left:12px;right:12px;top:64px;width:auto;z-index:150}
}

.bk-mdot{width:9px;height:9px;border-radius:50%;flex:0 0 auto;margin-left:18px}

/* ══════════════════════════════════════════════════════════════════════
   PINBOARD (§33, §34)
   The board keeps its geometry and the screen moves over it.
   ══════════════════════════════════════════════════════════════════════ */
.pin-vp{position:relative;width:100%;height:100%;min-height:0}
.pin-ctl{display:none}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* Full-bleed: a pinboard on a phone is the page. */
  /* Room at the foot for the page arrows, which must not sit on the canvas:
     the whole surface is a pan target, and a control floating in the middle
     of it is a control you hit while trying to move the board. */
  /* A pinboard page is exactly as tall as the spread plus its controls. The
     60dvh floor every other page gets would leave a band of empty paper
     between the zoom row and the page arrows. */
  /* By id, because two later rules set a dvh floor on the same two classes
     and a third would just be another thing to keep in order. */
  #bk-book.bk-spread:has(.bk-l-pinboard){min-height:0}
  .bk-l-pinboard{padding:10px 8px 54px;display:flex;flex-direction:column;gap:8px}
  .bk-l-pinboard .bk-page-hdr{height:auto;flex-wrap:wrap;gap:6px;margin:0}
  .bk-l-pinboard .bk-page-title{font-size:17px;padding:0 2px}
  /* One row that scrolls, not three that wrap: the board is the page, and
     every line the toolbar takes is a line the board does not get. */
  .bk-pin-tools{display:flex;gap:6px;width:100%;flex-wrap:nowrap;
    overflow-x:auto;scrollbar-width:none}
  .bk-pin-tools::-webkit-scrollbar{display:none}
  .bk-pin-tools .btn{min-height:38px;flex:0 0 auto}
  .bk-pin-tools .bk-page-more{margin-left:auto;flex:0 0 auto}

  /* touch-action:none is the whole of §34's second half. Without it the
     browser claims the gesture for page scrolling and the canvas gets a
     stream of pointercancel events instead of a pan. */
  /* The viewport takes the board's own proportions, so a landscape spread
     does not sit in a portrait box with a third of the screen empty above
     and below it. It is short — a spread across 390px is — and that is what
     the pinch is for. */
  /* height:auto or the base rule's height:100% wins and the aspect ratio is
     never consulted — the spread would sit in a portrait box again. */
  .pin-vp{overflow:hidden;touch-action:none;overscroll-behavior:contain;
    aspect-ratio:420/297;height:auto;min-height:0;max-height:60dvh;
    border-radius:12px;background:rgba(0,0,0,.16);
    border:1px solid var(--border)}
  .pin-vp > .bk-board{position:absolute;top:0;left:0;
    /* A design size, not the viewport: the board needs a real box for its
       percentage coordinates to mean anything, and it is then scaled to fit
       rather than squashed to the screen.

       420 x 297 — the SPREAD's own proportions, the same ones the desktop
       board has. A pinboard is spatial, and its shape is part of what is
       arranged: reshaping it for a phone would move every object relative to
       every other one, which is the one thing a board must never do. It
       opens fitted and is pinched into. */
    width:980px;height:693px;transform-origin:0 0;will-change:transform}

  /* Every control the board offers, at a size a finger can hit. The grip,
     the resize handle and the connector are 44px targets over their painted
     size — a 12px handle is a handle for a mouse. */
  .bk-pin-grip,[data-pin-resize],[data-pin-connect]{touch-action:none;position:relative}
  .bk-pin-grip::after,[data-pin-resize]::after,[data-pin-connect]::after{
    content:'';position:absolute;top:50%;left:50%;width:38px;height:38px;
    transform:translate(-50%,-50%)}
  /* Permanently visible: there is no hover to reveal them with (§47). */
  .bk-pin .bk-pin-grip,.bk-pin [data-pin-resize],.bk-pin [data-pin-connect]{opacity:1}

  /* A row of its own under the board. Nothing floats on a pan surface. */
  .pin-ctl{display:flex;align-items:center;gap:2px;align-self:center;
    padding:3px;border-radius:12px;background:var(--surface);
    border:1px solid var(--border)}
  .pin-ctl-b{min-width:44px;min-height:40px;display:grid;place-items:center;
    border-radius:9px;color:var(--text-2);font-size:16px;font-weight:600}
  .pin-ctl-b:active{background:rgba(255,255,255,.09)}
  .pin-ctl-fit{font-size:12.5px;padding:0 12px}
  .pin-ctl-z{min-width:44px;text-align:center;font-size:11.5px;color:var(--muted);
    font-variant-numeric:tabular-nums}
}

/* ══════════════════════════════════════════════════════════════════════
   PROJECTS (§25, §26)
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .pj-head,.pj-list{margin-left:0;max-width:none}
  .pj-head-row{flex-direction:column;align-items:stretch;gap:12px}
  .pj-head-side{justify-content:space-between}
  .pj-list{gap:18px}

  /* Title, next action, progress, lifecycle. The area, the focus and the
     target date are all on the project's own page — one tap, not gone. */
  .pj-row{padding:14px 12px 12px 14px;gap:8px}
  .pj-title{font-size:16.5px;white-space:normal}
  .pj-outcome{display:none}
  .pj-area,.pj-target{display:none}
  .pj-foot{gap:7px}
  /* Always visible: there is no hover to reveal it with (§47). */
  .pj-row .pj-more{opacity:1;width:var(--m-tap);height:var(--m-tap);margin-top:-2px}
  .pj-next-t{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;
    -webkit-box-orient:vertical;overflow:hidden}

  /* ── Detail ───────────────────────────────────────────────────────── */
  .pjd-head .pj-head-row{gap:8px}
  .pjd-left{min-width:0}
  .pjd-left h1{font-size:24px;white-space:normal}
  .pjd-meta{flex-wrap:wrap;gap:7px}
  .pjd-meta .cf-ctl{min-height:38px}
  .pjd-book{width:100%;justify-content:center;min-height:var(--m-tap)}
  .pj-head-side{width:100%;gap:8px}
}

/* ══════════════════════════════════════════════════════════════════════
   SETTINGS (§37)
   An index of pages, and a page. No secondary column.
   ══════════════════════════════════════════════════════════════════════ */
.set-index,.set-sub{display:block}
.set-idx{display:none}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .set-page{grid-template-columns:minmax(0,1fr);gap:0}
  .set-nav{display:none}
  .set-index{display:flex;flex-direction:column;
    background:var(--surface);border:1px solid var(--border);
    border-radius:var(--m-radius);overflow:hidden}
  .set-idx{display:flex;align-items:center;gap:12px;width:100%;text-align:left;
    min-height:60px;padding:11px 16px;color:var(--text);
    -webkit-tap-highlight-color:transparent}
  .set-idx + .set-idx{border-top:1px solid var(--hairline)}
  .set-idx:active{background:rgba(255,255,255,.06)}
  .set-idx-t{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
  .set-idx-l{font-size:15px;font-weight:600;letter-spacing:-.15px}
  .set-idx-b{font-size:12px;color:var(--muted);line-height:1.35}
  .set-idx-chev{flex:0 0 auto;width:8px;height:8px;color:var(--muted);
    border-right:1.7px solid currentColor;border-bottom:1.7px solid currentColor;
    transform:rotate(-45deg)}

  .set-back{display:inline-flex;align-items:center;gap:7px;min-height:var(--m-tap);
    margin:0 0 6px -8px;padding:0 10px;border-radius:11px;
    font-size:13.5px;font-weight:600;color:var(--text-2)}
  .set-back:active{background:rgba(255,255,255,.06)}
  .set-back-chev{width:8px;height:8px;border-left:1.8px solid currentColor;
    border-bottom:1.8px solid currentColor;transform:rotate(45deg)}
  .set-sub-head h2{font-size:22px}
  .set-row{flex-wrap:wrap;gap:8px;min-height:52px}
  .set-row .btn,.set-row .cf-ctl{min-height:var(--m-tap)}
  .set-item{min-height:52px}
}

/* ══════════════════════════════════════════════════════════════════════
   LIBRARY, DIARY AND HISTORY
   ══════════════════════════════════════════════════════════════════════ */
.lib-view{display:none}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .lib-bar{flex-direction:column;align-items:stretch;gap:10px;margin-bottom:16px}
  .lib-bar-right{margin-left:0;width:100%;gap:8px}
  .lib-search{flex:1;min-height:var(--m-tap)}
  .lib-search input{width:100%}
  .lib-filters{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;
    margin-inline:calc(-1 * var(--m-pad));padding-inline:var(--m-pad)}
  .lib-filters::-webkit-scrollbar{display:none}
  .lib-filters .chip{flex:0 0 auto}
  .lib-view{display:inline-flex;gap:2px;padding:2px;border-radius:11px;
    background:var(--surface-2);border:1px solid var(--border);flex:0 0 auto}
  .lib-view-b{min-height:38px;padding:0 13px;border-radius:9px;font-size:12.5px;
    font-weight:600;color:var(--muted)}
  .lib-view-b.on{background:var(--accent-soft);color:#fff}
  .lib-browse{display:flex;flex-direction:column;gap:22px}
  .lib-grid{grid-template-columns:minmax(0,1fr);gap:10px}
  .lib-card{min-height:0;padding:13px 14px 12px 18px}
  /* Always visible: no hover to reveal it with. */
  .lib-obj-more,.lib-card .lib-card-more{opacity:1}

  /* Diary already stacks — the writing above, the check-in below — and its
     day navigation is in the header where it is labelled. What it needs here
     is room and reachable controls. */
  .dia-book.bk-spread{max-width:none}
  .dia-left,.dia-right{padding:16px 14px}
  .dia-toolbar .btn,.dia-toolbar button{min-height:40px}

  .hist-row{grid-template-columns:20px minmax(0,1fr);row-gap:4px;min-height:52px}
  .hist-meta{grid-column:2}
}

/* ══════════════════════════════════════════════════════════════════════
   WHAT WALKING IT AT 390px FOUND
   Each of these was measured in a browser rather than reasoned about
   from the stylesheet, which is the only way any of them show up.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* Today overflowed the viewport by 51px. A flex item's `min-width` is
     `auto`, so the horizontally-scrolling habit row refused to shrink below
     its content and dragged its parent — and therefore the page — wider than
     the screen. The scroller is the thing that scrolls; its container is not. */
  .m-buckets > *{min-width:0;width:100%}
  .m-habits{min-width:0;overflow:hidden}
  /* The scroller is the thing that scrolls. Sized to its parent explicitly
     rather than trusting the cross-axis stretch: the row's content is wider
     than the screen by design, and anything that lets that width escape takes
     the whole page with it. */
  .m-habits-row{width:100%;max-width:100%;min-width:0}
  .m-toolbar .filters{min-width:0}

  /* Text buttons: the paint stays the size the design wants and the finger
     target is an overlay, because a 44px-tall project title would put three
     projects on a screen. */
  .pj-title,.pj-attention-act,.hist-restore,.dia-title{position:relative}
  .pj-title::after,.pj-attention-act::after,.hist-restore::after,.dia-title::after{
    content:'';position:absolute;top:50%;left:-6px;right:-6px;height:var(--m-tap);
    transform:translateY(-50%)}

  /* Wide enough already, just short. Real controls only: `.set-idx-chev` was
     in this list and is an 8px rotated-border arrow inside a 76px row — a
     min-height stretched it into a 42px diagonal stroke down the side of the
     card, which is the stray mark that showed up in Settings. The row is the
     tap target; the arrow is decoration and is aria-hidden. */
  .dia-prompt-a,.lib-view-b,.bk-tb{min-height:42px}
  .lib-view{padding:3px}
  .dia-prompt-a{padding:10px 12px}
}

/* The browse card's overflow. Painted small, hit big, always visible — there
   is no hover on a phone to reveal it with. */
.lib-card-more{margin-left:auto;width:30px;height:30px;flex:0 0 auto;position:relative}
.lib-card-more::after{content:'';position:absolute;top:50%;left:50%;
  width:44px;height:44px;transform:translate(-50%,-50%)}

/* ══════════════════════════════════════════════════════════════════════
   THE DENSITY PASS
   ══════════════════════════════════════════════════════════════════════

   The first mobile build was responsive and correct and still read as a
   desktop application that had been narrowed: a task carrying a title and a
   project name occupied 126px, so four of them filled a phone screen.

   The rule this section holds:

       PRESERVE ALL CAPABILITY AND INFORMATION.
       THE IMMEDIATE SURFACE IS COMPACT AND MADE FOR TOUCH.

   Nothing below removes an action. It removes the permanent FOOTPRINT of
   actions that already exist somewhere else — the two arrow buttons on every
   task row are in the ⋯ sheet, where they are labelled — and it takes the air
   out of everything that was breathing at desktop scale.

   Reduce empty padding, never the tap target. Where a control has to stay
   small to keep a row short, it keeps its painted size and grows a
   transparent 44px overlay. */

:root{
  /* ── The scale ────────────────────────────────────────────────────────
     Five numbers, and everything on a phone is expressed in them. */
  --m-row:60px;        /* a compact interactive row */
  --m-row-lg:76px;     /* a row carrying two lines and a number */
  --m-card:96px;       /* a card with real information in it */
  --m-sec:22px;        /* between sections */
  --m-list:8px;        /* between items in a list */
}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* Typography, once. The desktop task size is 13px; on a phone held at
     arm's length that is small, and the fix is a bigger FONT in a shorter
     row rather than the reverse. */
  :root{--fs-task:14.5px;--stack-gap:var(--m-sec)}
  .main-col{gap:var(--m-sec)}
  .main-wrap{gap:var(--m-sec)}

  /* ── One page title ───────────────────────────────────────────────────
     The top bar already says which section this is. `.page-actions` stays:
     Diary's date navigation and Calendar's controls live there. */
  .page-head.m-dupe{gap:0}
  .page-head.m-dupe > h1,
  .page-head.m-dupe > .eyebrow,
  .page-head.m-dupe > .sub{display:none}
  .page-head.m-dupe:empty{display:none}

  /* ── Task rows ────────────────────────────────────────────────────────
     One line for the title, one for the context, and the ⋯ back on the
     title line where it started. Around 60px instead of 126. */
  .t-row{grid-template-columns:auto minmax(0,1fr) auto;row-gap:0;gap:12px;
    align-items:center}
  /* The edge lives with the fill, at the top of this file. */
  .task{padding:9px 4px 9px 14px}
  .t-actions{grid-column:auto;justify-content:flex-end;gap:0;opacity:1;
    align-self:center}
  /* The arrows and the grip go. Both moves are the first two rows of the ⋯
     sheet, named — "Move earlier · To This Week" says more than a chevron
     ever did — and dragging is a mouse affordance. */
  .t-actions .t-btn[data-act="back"],
  .t-actions .t-btn[data-act="fwd"],
  .t-grip{display:none}
  .t-btn[data-act="menu"]{width:var(--m-tap);height:var(--m-tap)}
  .t-title{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;
    -webkit-box-orient:vertical;overflow:hidden;padding:0}
  .t-title::after{display:none}       /* the row itself is the target now */
  .t-meta{margin-top:2px;font-size:11.5px;gap:5px}
  .t-tick{width:22px;height:22px}
  .drop{display:flex;flex-direction:column;gap:var(--m-list);padding:0}
  .bucket{background:none;padding:0}

  /* ── Bucket heading, with the Add control in it ───────────────────────
     `TODAY  7  + Add` — one line, and manual capture is beside the thing it
     adds to instead of being a second full-width purple button competing
     with the assistant card above it. */
  .bucket-head{display:flex;align-items:center;gap:9px;margin:0 0 9px;
    padding:0 2px}
  .bucket-head h2{font-size:11px;font-weight:700;letter-spacing:.11em;
    text-transform:uppercase;color:var(--muted);margin:0}
  .m-add{margin-left:auto;display:inline-flex;align-items:center;
    min-height:34px;padding:0 12px;border-radius:9px;font-size:12.5px;
    font-weight:600;color:var(--accent);background:var(--accent-soft);
    position:relative}
  .m-add::after{content:'';position:absolute;top:50%;left:50%;
    width:calc(100% + 12px);height:var(--m-tap);transform:translate(-50%,-50%)}
  .m-add:active{background:rgba(var(--accent-rgb),.3)}

  /* ── One scrolling row of areas, and it looks deliberate ──────────────
     The mask is the difference between "there is more to the right" and "a
     chip got cut in half". */
  .m-toolbar{gap:0}
  .m-toolbar .filters{gap:7px;padding:2px var(--m-pad) 2px var(--m-pad);
    -webkit-mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 26px),transparent 100%);
    mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 26px),transparent 100%)}
  .m-toolbar .chip{min-height:36px;padding:0 13px;font-size:12.5px}
  .m-toolbar .chip[aria-pressed="true"]{background:var(--accent-soft);color:#fff;
    border-color:rgba(var(--accent-rgb),.5);font-weight:600}

  /* ── The day, above the board ─────────────────────────────────────────
     Next, the summary line and the assistant are the reason to pick the
     phone up. They keep their weight; they just stop being tall. */
  .m-next-row{min-height:var(--m-row-lg);padding:11px 13px}
  .m-next.is-clear{min-height:52px;padding:0 13px}
  .m-ai{gap:8px}
  .ai-invite{padding:12px 15px 12px 9px;gap:13px;border-radius:19px;
    background:
      radial-gradient(74% 130% at 14% 46%, rgba(146,100,255,.26) 0%, rgba(var(--accent-rgb),.05) 58%, rgba(var(--accent-rgb),0) 100%),
      linear-gradient(180deg, rgba(255,255,255,.035), rgba(255,255,255,0) 42%),
      var(--surface);
    border:1px solid rgba(var(--accent-rgb),.22);
    box-shadow:0 1px 2px rgba(0,0,0,.24), inset 0 1px 0 rgba(255,255,255,.05)}
  .orb-md{width:74px;height:74px}
  .ai-invite-say{font-size:14.5px}
  .ai-invite-sub{font-size:11.5px}
  .m-glance{font-size:13px}

  /* ── Habits ───────────────────────────────────────────────────────────
     `HABITS  0/7  ›` and one scrolling row of pills that ends on purpose.
     The pills used to reach the card's edge and stop mid-shape, which reads
     as a rendering fault rather than as a scroller. */
  .m-habits{padding:0 0 10px;border-radius:var(--m-radius)}
  .m-habits-h{min-height:46px;padding:0 12px 0 14px;gap:8px}
  .m-habits-row{gap:7px;padding:2px 14px 2px 14px;
    -webkit-mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 24px),transparent 100%);
    mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 24px),transparent 100%)}
  .m-hb{min-height:36px;padding:0 12px 0 8px;font-size:12.5px}

  /* ── Later ────────────────────────────────────────────────────────────
     `This Week  6  ›`. Already right; only the height changes. */
  .m-later{gap:var(--m-list)}
  .m-later-b > summary{min-height:var(--m-row);padding:0 13px;font-size:14px}
  .m-later-b .drop{padding:2px 8px 10px}
  .m-later .m-sec-h{margin-bottom:2px}

  /* ── Empty states, everywhere ─────────────────────────────────────────
     A short sentence and the action, on one row. The dashed rectangle with
     two lines of encouragement in it was 130px of a phone screen spent
     saying that nothing had happened. */
  .empty,.state,.pjd-empty,.bk-rail-empty,.rail-quiet{
    padding:12px 14px;min-height:0;border-radius:12px;font-size:13px;
    line-height:1.45;text-align:left}
  .empty{border-style:solid;border-color:var(--border);background:var(--surface)}
  .empty br{display:none}
  .empty .btn,.state .btn{min-height:38px;margin-top:8px}
  .state b{font-size:14px}
}

/* ── The centre action, and what it is ────────────────────────────────────
   It was a plain purple circle: the lotus was in it, drawn with the brand
   gradient, which is purple on purple and therefore invisible. The mark has
   to read as LIFE OS ASSISTANT at a glance, not as an unlabelled action.

   CSS beats a presentation attribute, so `fill` here overrides the
   `fill="url(#lotus26)"` the mark carries. */
.mnav-ai .logo-mark path{fill:#fff}
.mnav-ai .logo-mark path:nth-of-type(1),
.mnav-ai .logo-mark path:nth-of-type(2){fill-opacity:.66}
.mnav-ai .logo-mark path:nth-of-type(3){fill-opacity:1}
.mnav-ai .logo-mark{filter:drop-shadow(0 1px 3px rgba(24,10,60,.45))}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* ── The centre button, lit rather than glowing ──────────────────────
     It was a flat radial fill under one wide solid shadow, and a solid
     shadow is the thing that reads as cheap: the light had a findable edge,
     which nothing lit ever does.
     Same five ideas as the orb's body, in CSS. The bloom is a pseudo-element
     BEHIND the button so it can be soft without bleeding over the mark; the
     sheen is a pseudo-element in front of the fill and behind the mark. */
  .mnav-ai{
    /* The dark end of the ramp was #4E27B4 — 6.37% of white, which is navy
       once a phone's display adds saturation, and the button was reported as
       "a dark disc with a light ring". Raised to #6438D8 (10.50%), with the
       whole ramp lifted to match, so no part of the face can fall to navy. */
    background:
      radial-gradient(72% 68% at 32% 20%, rgba(255,255,255,.32) 0%, rgba(255,255,255,0) 62%),
      radial-gradient(120% 118% at 34% 22%, #C4A2FF 0%, #9E72FF 38%, #8355F2 68%, #6438D8 100%);
    /* The bloom is a SHADOW, not a pseudo-element.
       It was `::before { z-index:-1 }`, and the comment above it claimed the
       element was "BEHIND the button". It is not: `transform` makes .mnav-ai a
       stacking context, and inside one the element's own background paints
       BEFORE negative-z-index children — so the bloom was laid over the face.
       Measured here it only cost 1/255 because the veil is nearly the same
       violet as the button underneath, but that is luck, not design, and it is
       exactly the kind of thing that renders differently on another engine.
       A box-shadow is drawn outside the border-box by definition and cannot
       veil anything, on any browser. */
    /* Every ring-shaped declaration is gone.
       There was a `0 0 0 5px` cut-out meant to punch the bar away from the
       button, and two dark contact shadows underneath it. All three draw a
       hard-edged annulus, and an annulus is only invisible while it happens
       to match what is behind it — which is why this button kept being
       reported with a ring around it on a real phone and never here. The
       bar is one opaque colour now, so there is nothing to cut out of, and a
       shadow that only shows up on somebody else's display is not worth the
       depth it buys. What is left cannot produce an edge: two INSET shades
       for the sphere, and a soft glow with no spread step. */
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.42),          /* top rim */
      inset 0 -7px 12px -7px rgba(26,8,64,.85),     /* the ball turning away */
      0 0 18px 2px rgba(var(--accent-rgb),.34),            /* the bloom, behind */
      0 0 34px 10px rgba(var(--accent-deep-rgb),.16)}
  .mnav-ai::after{
    content:'';position:absolute;inset:0;border-radius:50%;z-index:1;
    background:linear-gradient(158deg,
      rgba(255,255,255,.24) 0%, rgba(255,255,255,.05) 30%, rgba(255,255,255,0) 54%)}
  .mnav-ai .logo-mark{position:relative;z-index:2}
  /* Current section: the same object with the light turned up, never a
     different component (§11) — the thing you are in should still look like
     the thing you pressed. */
  .mnav-ai[aria-current="page"]{
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.5),
      inset 0 -7px 12px -7px rgba(26,8,64,.85),
      0 0 22px 4px rgba(var(--accent-rgb),.5),
      0 0 40px 12px rgba(var(--accent-deep-rgb),.22)}
  .mnav-ai[aria-current="page"] .logo-mark path{fill-opacity:1}
}

@media (prefers-reduced-motion:reduce){.mnav-ai{transition:none}}

/* ── The assistant, tightened ─────────────────────────────────────────── */
.asst-note{display:flex;align-items:center;gap:8px;margin:0;
  font-size:12px;color:var(--muted);letter-spacing:.01em}
.asst-note .asst-dot{flex:0 0 7px;width:7px;height:7px;border-radius:50%;
  background:var(--warn);margin:0;box-shadow:0 0 0 3px rgba(255,196,77,.14)}
.asst-mic{flex:0 0 auto;gap:7px}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .asst{gap:14px;min-height:0}
  .asst-stage{padding:4px 0 0;gap:8px}
  .orb-lg{width:min(248px,66vw);height:min(248px,66vw)}
  .asst-say{font-size:15.5px}
  .asst-actions{gap:8px}
  .asst-actions .btn{min-height:46px}
  .asst-dev{padding:12px;margin-top:2px}
}

/* ── Settings and Reminders row density ──────────────────────────────── */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .set-idx{min-height:var(--m-row-lg);padding:10px 14px}
  .set-idx-b{font-size:11.5px}
  .set-sec{gap:12px}
  .set-card{border-radius:var(--m-radius)}
  .set-row{min-height:var(--m-row);padding:10px 14px}
  .set-item{min-height:var(--m-row);padding:8px 14px}
  .set-sec-h{margin-bottom:8px}

  /* Reminders: Pause and Edit move into the overflow, which every other row
     in the app already uses. The card keeps everything it said. */
  .rv-row{min-height:var(--m-row-lg);padding:10px 12px}
  .rv-actions .btn{min-height:38px}
  .hist-row{padding:10px 12px}
}

/* ── Today's greeting ─────────────────────────────────────────────────────
   The one heading on a phone that is NOT a repeat of the top bar, so it
   stays — but at 144px it was pushing the first task below the fold on its
   own. The sub-line goes: "Here is what is in front of you" is a promise the
   rest of the screen keeps for itself. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .page-head{gap:1px}
  .page-head .eyebrow{font-size:10.5px;letter-spacing:.12em}
  h1{font-size:23px;letter-spacing:-.5px;line-height:1.15}
  .page-head > .sub{display:none}
  .page-actions{margin-top:8px}
}

/* ── Titles that are nested ───────────────────────────────────────────────
   Calendar and Projects render their own headers INTO `.page-head`, so their
   h1 is not a direct child and the rule above missed it. A detail page's h1
   is the name of the THING — a project, a book — and must never be hidden. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .page-head.m-dupe .cal-head h1,
  .page-head.m-dupe .pj-head:not(.pjd-head) h1,
  .page-head.m-dupe .lib-page ~ h1{display:none}

  /* ── §23 Calendar chrome ──────────────────────────────────────────────
     292px before the calendar began. Three stacked rows of controls with
     desktop gaps between them, above a title that the top bar had already
     said. What is left is: the period and Today, the four modes, the layer
     filters — the same controls, on three tight rows. */
  .cal-head{gap:9px}
  .cal-head-row{gap:9px}
  .cal-head-main{gap:0;min-width:0}
  .cal-period{gap:4px;min-width:0}
  /* §7 The label is never shortened by the layout.
     It used to be `flex:1 1 0%; min-width:0`, which let the row squeeze it to
     93px and ellipsis the last two characters of every ordinary state —
     `Next 60 d…`, `August 20…`. The LABELS are short now (periodLabel writes
     a phone version of each), so the rule is the opposite: it takes the width
     its own text needs and the row gives way. If something one day genuinely
     does not fit, the row wraps, which is honest; an ellipsis on a date is
     not. */
  .cal-period-label{flex:0 1 auto;min-width:max-content;font-size:15px;
    font-weight:650;letter-spacing:-.2px;white-space:nowrap}
  .cal-nav{width:38px;height:38px}
  .cal-today{padding:0 9px;font-size:12px}
  .cal-add span{display:none}            /* the + says it */
  .cal-add{padding:0;width:38px;justify-content:center}
  .cal-nav{width:var(--m-tap);height:var(--m-tap);flex:0 0 auto}
  .cal-today{min-height:36px;padding:0 12px;font-size:12.5px;flex:0 0 auto}
  .cal-head-side{gap:6px;flex:0 0 auto}
  .cal-add{min-height:36px;padding:0 12px;font-size:12.5px}
  /* The period row and the Add control share one line. */
  /* `flex-direction:row` said out loud: app.css turns this row into a column
     below 900px, and setting `display:flex` alone leaves that direction in
     place — so the three groups each took a line and the calendar started
     292px down the page. */
  .cal-head-row:first-child{display:flex;flex-direction:row;align-items:center;
    flex-wrap:wrap;row-gap:9px;gap:8px}
  /* Two rows, not three. The period and the Add control share the first
     line — they are both "what am I looking at and what would I add to it" —
     and the four modes get the second to themselves, because four tabs
     across 390px need the whole width to stay readable. */
  .cal-head-main{flex:1 1 0%;order:1;min-width:0}
  .cal-head-side{order:2;flex:0 0 auto;margin-left:auto;justify-content:flex-end}
  .cal-head-mid{flex:1 1 100%;order:3;min-width:0}
  .cal-head-main .cal-period{display:flex;align-items:center}
  /* Layers: one scrolling row that ends on purpose, like every other chip
     row on the phone. */
  .cal-head-sub{margin:0}
  .cal-layers{display:flex;flex-wrap:nowrap;overflow-x:auto;gap:6px;
    scrollbar-width:none;
    margin-inline:calc(-1 * var(--m-pad));padding-inline:var(--m-pad);
    -webkit-mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 24px),transparent 100%);
    mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 24px),transparent 100%)}
  .cal-layers::-webkit-scrollbar{display:none}
  .cal-layer{flex:0 0 auto;min-height:36px;padding:0 11px;font-size:12px}

  /* ── §25 Projects: a deliberate tab row, not six squeezed buttons ────── */
  .pj-head{gap:10px}
  .pj-filters{gap:7px;margin-inline:calc(-1 * var(--m-pad)) 0;
    padding-inline:var(--m-pad) 0;
    -webkit-mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 24px),transparent 100%);
    mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 24px),transparent 100%)}
  .pj-filter{min-height:38px;padding:0 13px;font-size:13px;white-space:nowrap}
  .pj-head-side{width:auto}
  .pj-head-side .btn{min-height:38px;padding:0 12px;font-size:12.5px}

  /* The h1 is hidden on a phone — the bar above already says "Projects" —
     which left New project alone on a 56px row of its own. It joins the
     lifecycle strip instead: the strip scrolls in what is left, and the fade
     now ends against the button rather than against the screen edge, so the
     cut reads as "there is more" instead of as a clipped word. */
  .pj-head:not(.pjd-head){flex-direction:row;align-items:center;gap:10px}
  .pj-head:not(.pjd-head) .pj-head-row{order:2;flex:0 0 auto;width:auto;gap:0}
  .pj-head:not(.pjd-head) .pj-filters{order:1;flex:1 1 auto;min-width:0;
    overflow-x:auto;scrollbar-width:none}
  .pj-head:not(.pjd-head) .pj-filters::-webkit-scrollbar{display:none}

  /* §24 Project cards stay compact: title, next action, a little status. */
  .pj-row{padding:12px 10px 11px 13px;gap:6px}
  .pj-main{gap:4px}
  .pj-title{font-size:15.5px}
  .pj-foot{margin-top:1px;gap:6px}
  .pj-progress{font-size:11px}
  .pj-state,.pj-focus{font-size:10px;padding:2px 6px}
  .pj-list{gap:14px}
}

/* ── Project cards: the attention line stops being a paragraph ────────────
   The "why" sentence wrapped to three lines and took 68px of a 185px card,
   which is a card explaining itself at length on a screen where the point is
   to scan six projects. The label and the action stay — they are the signal
   and the way to act on it — and the sentence is clamped to one line. The
   whole of it is on the project's own page. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .pj-attention{flex-wrap:nowrap;align-items:center;gap:7px;min-width:0}
  .pj-attention-l{font-size:10px;padding:2px 6px;flex:0 0 auto}
  .pj-attention-w{flex:1 1 auto;min-width:0;font-size:12px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .pj-attention-act{flex:0 0 auto;font-size:12px;position:relative}
  .pj-attention-act::after{content:'';position:absolute;top:50%;left:-6px;right:-6px;
    height:var(--m-tap);transform:translateY(-50%)}
  .pj-row .pj-more{align-self:flex-start}
}

/* ── §26 The Project Book, at the size of a link rather than a poster ─────
   The header button's SVG carried a viewBox and no dimensions. On a desktop
   it sat inside an auto-width button and nobody noticed; stretched to the
   full width of a phone it grew to fill the button, and a book illustration
   took a third of the first screen of every project. The icon has a size
   now, and on a phone the whole action moves below Tasks as a compact card. */
.pjd-book svg{flex:0 0 18px;width:18px;height:18px}
.pjd-book-sec{display:none}

.pjd-book-card{display:flex;align-items:center;gap:12px;width:100%;text-align:left;
  min-height:72px;padding:12px 14px;border-radius:var(--m-radius);
  background:var(--surface);border:1px solid var(--border);color:var(--text)}
.pjd-book-card:active{background:var(--surface-2)}
.pjd-book-ico{flex:0 0 40px;width:40px;height:40px;display:grid;place-items:center;
  border-radius:11px;background:var(--accent-soft);color:var(--accent)}
.pjd-book-t{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.pjd-book-l{font-size:10.5px;font-weight:700;letter-spacing:.11em;
  text-transform:uppercase;color:var(--muted)}
.pjd-book-s{font-size:15px;font-weight:600;letter-spacing:-.15px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pjd-book-go{flex:0 0 auto;color:var(--muted);font-size:20px;line-height:1}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .pjd-book-sec{display:block}
  #pjd-book{display:none}          /* the header copy; the card replaces it */

  /* ── §26/§28 the detail page, in execution order ─────────────────────── */
  .pjd-head{gap:9px}
  .pjd-left h1{font-size:21px}
  .pjd-outcome{font-size:12.5px;-webkit-line-clamp:2;display:-webkit-box;
    -webkit-box-orient:vertical;overflow:hidden}
  .pjd-meta{gap:6px}
  .pjd-health{gap:5px;font-size:12px}
  .pjd-body{gap:18px}
  .pjd-sec{gap:9px}
  .pjd-sec-head{gap:8px;flex-wrap:wrap}
  .pjd-sec-actions .btn{min-height:36px;padding:0 11px;font-size:12.5px}
  .pjd-notes{min-height:110px}
  .pjd-tasks{gap:var(--m-list)}

  /* Empty states: a sentence and the action, on one card. Not 150px of
     dashed rectangle announcing that nothing has happened. */
  .pj-empty,.pj-empty-inline{display:flex;flex-direction:column;align-items:flex-start;
    gap:6px;padding:13px 14px;min-height:0;border-radius:12px;text-align:left;
    border:1px solid var(--border);background:var(--surface)}
  .pj-empty-t{font-size:13.5px;font-weight:600}
  .pj-empty-s{font-size:12px;color:var(--muted)}
  .pj-empty-a{min-height:36px;margin-top:2px}
  .pjd-next.is-empty{padding:12px 14px;min-height:0}
}

/* ── The project card is a ROW again ──────────────────────────────────────
   app.css turns `.pj-row` into a column below 900px, from a time when the ⋯
   was revealed on hover and had nowhere sensible to sit. Two consequences,
   both invisible until measured: the ⋯ dropped onto its own line and added
   48px to every card, and `align-items:flex-start` in a column container
   means children size to MAX-CONTENT — so `.pj-main` laid out at 446px
   inside a 362px card and pushed the page sideways.

   Back to a row: title and status on the left, ⋯ on the right, where it is
   on every other list in the application. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .pj-row{flex-direction:row;align-items:flex-start;gap:10px}
  .pj-main{min-width:0;width:auto;flex:1 1 0%}
  .pj-row .pj-more{flex:0 0 var(--m-tap);margin-top:-4px;margin-right:-6px}
}

/* ── §30/§32 Library Browse rows ─────────────────────────────────────────
   The Diary is a row here, not a single cover standing in an empty shelf. */
.lib-row-card{display:none}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .lib-row-card{display:flex;align-items:center;gap:12px;width:100%;text-align:left;
    min-height:var(--m-row-lg);padding:11px 14px;border-radius:var(--m-radius);
    background:var(--surface);border:1px solid var(--border);color:var(--text)}
  .lib-row-card:active{background:var(--surface-2)}
  .lib-row-ico{flex:0 0 40px;width:40px;height:40px;display:grid;place-items:center;
    border-radius:11px;background:rgba(182,155,240,.16);color:var(--a-lavender)}
  .lib-row-t{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
  .lib-row-title{font-size:15px;font-weight:600;letter-spacing:-.15px}
  .lib-row-meta{font-size:11.5px;color:var(--muted);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .lib-row-go{flex:0 0 auto;color:var(--muted);font-size:20px;line-height:1}

  /* Cards: a row's worth of height, not a tile's. */
  .lib-sec{margin-bottom:0}
  .lib-sec-h{font-size:11px;letter-spacing:.11em;margin-bottom:9px}
  .lib-sec-n{font-weight:400;letter-spacing:0;text-transform:none;opacity:.7}
  .lib-card{min-height:var(--m-row-lg);padding:11px 12px 11px 17px;gap:4px;
    border-radius:var(--m-radius)}
  .lib-card-top{gap:7px}
  .lib-card-type{font-size:9.5px}
  .lib-card-title{font-size:15px;line-height:1.25}
  .lib-card-desc{display:none}
  .lib-card-foot{margin-top:2px;font-size:11px}
  .lib-browse{gap:20px}
}

/* ── §36/§37 Reading, then writing ───────────────────────────────────────
   The toolbar is not there while you read. It arrives when an editor takes
   focus and sits on the keyboard, where the caret is. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .bk-toolbar{display:none}
  html.bk-editing .bk-toolbar{display:flex;position:fixed;left:0;right:0;
    bottom:var(--kb-h,0px);z-index:140;margin:0;border-radius:0;
    background:rgba(23,21,31,.97);backdrop-filter:blur(14px);
    border-top:1px solid var(--border);
    padding:6px 8px calc(6px + env(safe-area-inset-bottom,0px));
    flex-wrap:nowrap;overflow-x:auto;gap:4px;scrollbar-width:none}
  html.bk-editing .bk-toolbar::-webkit-scrollbar{display:none}
  html.bk-editing .bk-toolbar .bk-tb{flex:0 0 auto;min-width:42px;min-height:42px}
  html.bk-editing .bk-toolbar .bk-tb-style{flex:0 0 auto;min-height:42px}
  /* Room under the last line for it, and the bottom bar steps aside. */
  html.bk-editing .bk-page{padding-bottom:62px}
  html.bk-editing .mnav{display:none}

  /* §33 The page reflows. Not an A4 spread scaled down — text at reading
     size, phone margins, and the paper identity kept: the ruled surface, the
     margin stripe and the Playfair headings are all still here. */
  .bk-page{font-size:15.5px;line-height:1.62}
  .bk-page-hdr{height:auto;min-height:38px}
  .bk-page-title{font-size:17px}
  .bk-book.bk-spread:not(.dia-book){min-height:64dvh}
  .bk-foot{padding:8px 0 0;font-size:11.5px}
}

/* ══════════════════════════════════════════════════════════════════════
   §40 THE DIARY, AS TWO PAGES ONE ABOVE THE OTHER
   The semantic stays: page one is what you write, page two is what you tap.
   What changes is that they are stacked rather than facing — and stacked
   with a visible seam, so it still reads as a diary being turned rather
   than as a web form with a textarea at the top.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .dia-book.bk-spread{max-width:none;gap:0;border-radius:14px;overflow:hidden}
  .dia-left,.dia-right{padding:16px 15px 18px}

  /* The seam. A dashed hairline said "two boxes"; this says "the next page",
     which is what it is — the page edge, its stitching, and a label. */
  .dia-right{border-top:0;position:relative;
    background:linear-gradient(180deg,rgba(0,0,0,.16),transparent 34px),var(--paper-2)}
  .dia-right::before{content:'CHECK-IN';position:absolute;top:0;left:0;right:0;
    height:26px;display:flex;align-items:center;padding-left:15px;
    font-size:9.5px;font-weight:700;letter-spacing:.16em;color:var(--paper-ink-2);
    border-top:1px solid rgba(255,255,255,.09);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
  .dia-right > *{margin-top:26px}
  .dia-right > * ~ *{margin-top:0}

  .dia-date{font-size:20px}
  .dia-head{gap:6px}
  .dia-toolbar{gap:4px;padding:4px 0}
  .dia-chip{min-height:38px;padding:0 11px}
  .dia-prompt-a{font-size:13px}
  /* Date navigation and History stay in the header where they are labelled
     (§41) — never behind a menu. */
  .dia-daynav{display:flex;align-items:center;gap:4px}
  .dia-step,.dia-today{min-height:var(--m-tap)}

  /* ── §20 Calendar Day: what is grouped is obvious ─────────────────────
     The all-day items and the timed reminders shared one unlabelled strip
     above the grid, so a birthday and a 4pm reminder looked like the same
     kind of thing stacked in the same box. */
  .cal-plan.is-day .pl-allday,.cal-plan.is-three .pl-allday{
    position:relative;gap:4px;margin:0 0 8px;padding:20px 0 8px;
    border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline)}
  .cal-plan.is-day .pl-allday::before,.cal-plan.is-three .pl-allday::before{
    content:'ALL DAY';position:absolute;top:4px;left:0;
    font-size:9px;font-weight:700;letter-spacing:.14em;color:var(--muted)}
  .cal-plan.is-day .pl-ad,.cal-plan.is-day .pl-rem-ad{min-height:34px;
    display:flex;align-items:center;font-size:12.5px;padding:0 9px}
  .cal-plan.is-day .pl-day-head{height:auto;padding-bottom:8px;gap:8px}
  .cal-plan.is-day .pl-canvas{position:relative}
}

/* ── A phone on its side ──────────────────────────────────────────────────
   390px of height, 60 of which is the navigation. Everything that was sized
   against WIDTH has to answer to height as well here, or the orb alone takes
   two thirds of the screen. */
@media (max-height:500px) and (max-width:1099px){
  :root{--m-nav-h:52px}
  .mnav-ai{width:48px;height:48px;transform:translateY(-8px)}
  .mnav-ai:active{transform:translateY(-8px) scale(.93)}
  .mnav-ai svg{width:22px;height:22px}
  .mnav-i{font-size:10px}
  .orb-lg{width:min(248px,66vw,46dvh);height:min(248px,66vw,46dvh)}
  .asst{gap:10px}
  .asst-script{max-height:26dvh}
  .m-ai{flex-direction:row;align-items:center;gap:12px}
  .m-glance{flex:0 0 auto}
  .ai-invite{flex:1;min-width:0}
  .orb-md{width:58px;height:58px}
  .msheet{max-height:94dvh}
  .page-head{gap:0}
  h1{font-size:20px}
}

/* ══════════════════════════════════════════════════════════════════════
   WHAT THE SCREENSHOTS SHOWED
   Three things that only became obvious once the phone layout was
   photographed rather than measured.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* 1. The orphaned overflow.
     `.page-actions` holding a single ⋯ took a whole 50px row under the
     greeting and under a project's title — a lone dot-dot-dot floating in
     the middle of nowhere. It belongs on the heading line, at the top right,
     which is where the same control sits on every other surface.
     Scoped with :has() so Diary's date navigation — several controls, and
     genuinely its own row — is untouched. */
  .page-head{position:relative}
  .page-actions:has(> .util-btn:only-child){position:absolute;top:0;right:0;
    width:auto;margin:0}
  .page-head.m-dupe .page-actions:has(> .util-btn:only-child){top:-4px}
  .page-actions .util-btn{width:var(--m-tap);height:var(--m-tap)}

  /* A project's title row keeps its overflow beside the title too, rather
     than stretching the side group across the full width below it. */
  .pjd-head .pj-head-row{flex-direction:row;align-items:flex-start;gap:10px}
  .pjd-head .pjd-left{flex:1;min-width:0}
  .pjd-head .pj-head-side{width:auto;flex:0 0 auto}

  /* 2. One empty state, not an empty state inside an empty state.
     `.pjd-tasks.is-empty` draws a dashed frame and the card inside it draws
     a solid one, so the compact card ended up centred inside a large dashed
     rectangle — exactly the decoration §28 asked to remove. */
  .pjd-tasks.is-empty{border:0;padding:0;background:none}
  .pj-empty,.pj-empty-inline{padding:13px 14px}

  /* 3. A Book is for reading.
     The top bar carries the Book's name (see library-view.js) and the Book's
     own bar carries the way back, the section and the page. The page header
     between them was a third row saying the same things. */
  body:has(.bk-mbar) .page-head{display:none}
}

/* The empty card fills the row it is in. Centred, it read as a floating
   panel inside a container rather than as the state of the list. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .pj-empty,.pj-empty-inline{align-self:stretch;width:100%;align-items:flex-start}
  .pj-empty-s{max-width:none}
}

/* ══════════════════════════════════════════════════════════════════════
   FINAL VISUAL QA
   Seventeen screenshots, read carefully, and the defects they showed.
   Nothing here changes an approved composition; each is a geometry or a
   contrast fault in something that was otherwise right.
   ══════════════════════════════════════════════════════════════════════ */

/* ── §1 The top bar is opaque ─────────────────────────────────────────────
   The bar was `rgba(20,18,30,.86)` with a blur behind it, and a sticky bar
   you can see through does not hide what scrolls under it — it smears it. A
   project's title passing beneath read as a title cut in half.

   The layout was never wrong: the bar is sticky at the top of the scrolling
   column and the content column starts beneath it, which is exactly right.
   What was wrong was that the bar was not a surface. It is one now — no
   z-index changed, no offset invented, and one fewer compositing layer for a
   phone to maintain, which the blur was costing on every scroll frame. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .mobile-bar{background:var(--m-bar);backdrop-filter:none;
    border-bottom:1px solid rgba(255,255,255,.06)}
  /* And a shadow once something is behind it, so the edge is legible
     against content rather than only against the page. */
  .mobile-bar{box-shadow:0 1px 0 rgba(0,0,0,.35)}

  /* §19 The bar goes back. */
  .mobile-bar.has-back .logo-mark{display:none}
  .m-back{margin-left:-6px}
  .m-back svg{width:22px;height:22px}
}

/* ── §3 The confirmation is a footer, not something floating over cards ──
   It was sticky inside the scrolling review, which meant it sat ON TOP of
   whatever card happened to be under it — the last proposal was never fully
   readable, and the button competed with the navigation beneath it.

   A real footer instead: fixed above the navigation, with the review given
   exactly its height as bottom padding, so every card scrolls clear of it.
   One shared number, `--m-cta-h`, does both jobs — the alternative is a
   margin tuned to one demo that is wrong for every other list length. */
:root{--m-cta-h:76px}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .asst-confirm{position:fixed;left:0;right:0;
    bottom:calc(var(--m-nav-h) + var(--m-safe-b));z-index:118;
    display:flex;gap:10px;align-items:center;
    height:var(--m-cta-h);
    /* The extra bottom padding is the centre navigation button, which is 56px
       lifted 10px above the bar and ringed — it reaches into the bottom of
       anything sitting directly on top of the bar. The buttons sit above its
       reach; the bar's own background still runs behind it, which is what it
       is supposed to do. */
    padding:10px var(--m-pad) 22px;
    background:var(--m-bar);border-top:1px solid var(--border);
    box-shadow:0 -10px 24px -12px rgba(0,0,0,.7)}
  .asst-confirm .btn{min-height:44px}
  /* The room it takes, given back to the list above it. */
  .asst[data-state="proposal"] .asst-review{padding-bottom:calc(var(--m-cta-h) + 14px)}
  /* A keyboard replaces the navigation as the thing below, so the footer
     sits on the keyboard instead. */
  html.kb-open .asst-confirm{bottom:var(--kb-h,0px)}

  /* §5 The development panel is last, and it is labelled as development.
     It also clears the footer, which is the one thing that could have made
     it interfere with ordinary testing. */
  .asst[data-state="proposal"] .asst-dev{display:none}
  .asst-dev{border-style:dashed;border-color:var(--border-strong);opacity:.9}
  .asst-dev h2::before{content:'⚙ ';opacity:.6}
}

/* ── §6 Habits: a preview, not a sliced scroller ─────────────────────────
   The pills scrolled correctly and looked broken: a pill cut through the
   middle of "Read 20 pages" reads as a rendering fault however deliberate
   the mask behind it is. Today only needs a PREVIEW — the whole list, with
   streaks and editing, is one tap away in the sheet — so it shows the first
   three as complete rows and says how many more there are. Nothing is cut. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .m-habits-row{display:flex;flex-direction:column;gap:2px;overflow:visible;
    padding:2px 8px 0;-webkit-mask-image:none;mask-image:none}
  .m-hb{width:100%;min-height:42px;justify-content:flex-start;gap:11px;
    padding:0 10px;border:0;background:none;border-radius:10px;
    font-size:14px;color:var(--text)}
  .m-hb:active{background:rgba(255,255,255,.05)}
  .m-hb .hb-ring{flex:0 0 26px;width:26px;height:26px}
  .m-hb .hb-ring>svg.hr-svg{width:26px;height:26px}
  .m-hb.is-done{background:none}
  .m-hb.is-done .m-hb-n{color:var(--muted);text-decoration:line-through;
    text-decoration-color:rgba(255,255,255,.28)}
  .m-hb-n{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
}

/* ── §14 Check-in options that can be read ───────────────────────────────
   `Medium` needed 38px and had 30. The answer is not a smaller font — it is
   giving the chips the width the page already has: the rhythm rows put their
   label above the scale rather than beside it, which turns a 184px track
   into a 284px one, and the chips lose the padding they did not need. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* Four scales of four options each. One button per row turned that into a
     column forty rows deep; a 2 x 2 grid says the same thing in a quarter of
     the height and gives all four categories one interaction to learn.

     The width is STATED rather than inferred: `auto-fit` was sizing the
     track from its own content, which in a shrink-to-fit box settles on one
     column and produced exactly the tall form this replaces. */
  .dia-rhythm{display:flex;flex-direction:column;gap:14px}
  .dia-rh-row{display:flex;flex-direction:column;gap:7px;align-items:stretch;width:100%}
  .dia-chips-rhythm{width:100%;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}
  .dia-rh-l{font-size:10px}
  .dia-chip{padding:0 4px}
  .dia-chip-t{font-size:11px;letter-spacing:-.1px}
  .dia-rh-row .dia-chip{min-height:34px;padding:5px 3px;font-size:11px}
  .dia-chips{gap:5px}

  /* ── §13 The page boundary ───────────────────────────────────────────
     A real label in the check-in's own header, not a generated one that
     could not be measured and wrapped to CHECK- / IN. The seam is a gap and
     an edge: page one ends, page two begins. */
  .dia-book.bk-spread{gap:0;overflow:visible}
  .dia-right::before{content:none}
  .dia-right > *{margin-top:0}
  .dia-right{margin-top:12px;border-top:1px solid rgba(255,255,255,.10);
    border-radius:0 0 14px 14px;
    box-shadow:inset 0 12px 22px -18px rgba(0,0,0,.9);
    background:linear-gradient(180deg,rgba(0,0,0,.14),transparent 40px),var(--paper-2)}
  .dia-left{border-radius:14px 14px 0 0}
  .dia-ci-page{margin:0 0 3px;font-size:9.5px;font-weight:700;letter-spacing:.18em;
    text-transform:uppercase;color:var(--paper-ink-2);white-space:nowrap}
  .dia-ci-head{padding-top:2px}
  .dia-ci-title{font-size:19px}

  /* The Diary's header says nothing about the date.

     It said "Wednesday, 26 August 2026" directly above the day navigation —
     and the page beneath it, which is the diary itself, prints exactly the
     same civil date as its own heading. The one on the page is part of the
     book and stays; the one in the header was a copy of it three lines
     above, pushing the writing surface down the screen for nothing.

     Nothing is lost: previous / Today / next, the month and History are all
     still there, and that row is now the whole of the header. */
  body:has(.dia-page) .page-head.m-dupe > .sub{display:none}
}
.dia-ci-page{display:none}
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .dia-ci-page{display:block}
}

/* ── §10 Library's toolbar, on two deliberate rows ───────────────────────
   Five controls of different kinds were sharing one line: type filters, a
   view switch, a search field and Archived. Search ended up narrow enough
   that its own placeholder was clipped, which is the one control on the
   screen that has to look like it can be typed into.

   Row one: the type filters, scrolling, full bleed.
   Row two: search taking the width it needs, then Browse/Shelf, then
   Archived — the two least-used controls, at the end, at a size a thumb can
   still hit. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .lib-bar{gap:9px}
  .lib-bar-right{flex-wrap:wrap;gap:8px}
  .lib-search{order:1;flex:1 1 100%;min-height:44px;padding:0 13px}
  .lib-search input{width:100%;font-size:14px}
  .lib-view{order:2;flex:0 0 auto}
  .lib-bar-right > .btn{order:3;flex:0 0 auto;min-height:38px;padding:0 13px;
    font-size:12.5px}

  /* §19 One way back, and it is the chevron in the bar. */
  .set-back{display:none}
  .set-sub-head h2{display:none}
  .set-sub-head p{margin-top:0}
  .set-sub-head{margin-bottom:2px}

  /* §20 Completed: the gap above the first row was a heading's worth of
     space with no heading in it. */
  .page-head.m-dupe .page-actions{position:static;width:auto}
  body:has(.hist-list) .page-head.m-dupe{margin-bottom:-8px}
  .hist-group{margin-top:14px}
}

/* ── §21 One bottom inset, in one place ──────────────────────────────────
   Every scrolling page clears the navigation through `--m-bottom-clear` on
   `.main-wrap`, which is the shell. The two surfaces that do NOT scroll
   inside it — a sheet, and the assistant's fixed footer — carry the safe
   area themselves. Nothing gets its own hand-tuned padding-bottom. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .main-wrap{padding-bottom:var(--m-bottom-clear)}
  /* A Book's page and a pinboard are the tallest things on the phone and
     the likeliest to end underneath the bar; they inherit the same number
     rather than inventing one. */
  .bk-layout,.lib-page,.dia-page{padding-bottom:0}
}

/* ── §12 The Diary's controls on one row ─────────────────────────────────
   Previous / Today / next, the month, and History — five controls that were
   wrapping to two lines and costing 60px above a writing surface. They fit
   on one: the month button keeps its label, History keeps its word, and the
   arrows are the 44px they have to be. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .dia-actions{display:flex;flex-wrap:nowrap;align-items:center;gap:6px;
    width:100%;margin-top:6px}
  .dia-daynav{flex:0 0 auto;gap:2px;padding:0;background:none}
  .dia-actions #dia-jump{flex:1 1 auto;min-width:0;justify-content:center;
    min-height:38px;padding:0 8px}
  .dia-actions #dia-jump span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .dia-actions #dia-history{flex:0 0 auto;min-height:38px;padding:0 11px}
  .dia-step,.dia-today{min-height:38px}
  .dia-step{width:38px}
  .dia-today{padding:0 10px}
}

/* ── The metadata line stops abbreviating the one thing that identifies ──
   `Personal · 29 Aug · Ra… · NEXT ACTION`. Four things competing for 250px,
   and the one that lost was the project name — cut to two letters and an
   ellipsis, which is not a shorter version of the information, it is the
   absence of it.

   The line wraps instead. It stays one line whenever it fits, and when the
   project name would be squeezed it takes the second line whole:

       Personal · 29 Aug
       Rage 2026 planning   NEXT ACTION

   Fifteen pixels on the tasks that have a project and none on the ones that
   do not. The card stays compact and the project stays identifiable. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .t-meta{flex-wrap:wrap;row-gap:3px;white-space:normal;overflow:visible;
    text-overflow:clip;align-items:center}
  /* The name takes what it needs, and only ellipsises if it alone is wider
     than the whole row — which is honest rather than arbitrary. */
  .tm-project{max-width:100%;overflow:hidden;text-overflow:ellipsis;
    white-space:nowrap;flex:0 1 auto}
  .tm-next,.tm-area,.tm-due{flex:0 0 auto}
  /* A separator must never be the last thing on a line.
     `:last-child` was not enough: the dot before NEXT ACTION is not last in
     the list, it is only last on the LINE once the badge wraps beneath it —
     which it does on exactly the rows this change is for. */
  .t-meta .tm-sep:last-child,
  .t-meta .tm-sep:has(+ .tm-next){display:none}
}

/* ── The right margin was wider than the left ────────────────────────────
   Calendar, Projects, Library, Settings and Diary each collapse the rail by
   setting `grid-template-columns: minmax(0,1fr) 0` — which leaves a zero
   column, and a zero column still gets the grid GAP. Every one of those
   pages was therefore inset 14px further on the right than on the left: not
   enough to notice on one screen, and unmistakable once five of them are
   photographed side by side. One column on a phone, and no gap to lose. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  body:has(.lib-page) .main-wrap,
  body:has(.cal-head) .main-wrap,
  body:has(.pj-head) .main-wrap,
  body:has(.set-page) .main-wrap,
  body:has(.dia-page) .main-wrap{grid-template-columns:minmax(0,1fr)}
}

/* ══════════════════════════════════════════════════════════════════════
   POLISH PASS
   Spacing, light, and the small consistencies that separate a mobile
   layout from a mobile PRODUCT.
   ══════════════════════════════════════════════════════════════════════ */

/* ── Today's vertical rhythm ─────────────────────────────────────────────
   Next, the day in a line, the assistant, the areas and the board were four
   blocks stacked with nothing between them — measured, the gaps were 0, 0
   and 0, because each is a direct child of the scrolling column and only
   `.main-col` had a gap, which applies one level up.

   One rule, on the column that holds them, so the rhythm is a decision
   rather than the sum of whatever margins the blocks happened to carry. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  #main-scroll:has(> .m-next){display:flex;flex-direction:column;gap:20px}
  .m-ai{gap:10px}
  .m-next{gap:8px}
  /* The area chips sit closer to the board they filter than to the card
     above them — proximity is what says which of the two they belong to. */
  #main-scroll:has(> .m-next) > .m-toolbar{margin-bottom:-6px}
  .m-buckets{gap:16px}
  .bucket-head{margin-bottom:10px}
  .m-sec-h{font-size:10.5px;letter-spacing:.13em}

  /* ── Habits, as a rank of rings you scroll ────────────────────────────
     The ring is the same shape the desktop rail uses, at the size a thumb
     wants, with the name beneath it.

     A ROW, not a grid of six with a "3 more · See all" underneath. The
     button was a second way to reach a page that the header chevron already
     reaches, and it cost a line to say so; a row you push sideways shows
     every habit due today and needs no explanation. The fade at the right
     edge is the only affordance it needs. */
  .m-habits{padding:2px 0 12px}
  .m-habits-row{display:flex;flex-direction:row;grid-template-columns:none;gap:2px;
    align-items:flex-start;justify-content:flex-start;
    padding:4px 10px 2px;overflow-x:auto;overflow-y:visible;
    scroll-snap-type:x proximity;scrollbar-width:none;
    -webkit-overflow-scrolling:touch;
    -webkit-mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 26px),transparent 100%);
    mask-image:linear-gradient(90deg,#000 0,#000 calc(100% - 26px),transparent 100%)}
  .m-habits-row::-webkit-scrollbar{display:none}
  .m-hb{flex:0 0 92px;scroll-snap-align:start;
    flex-direction:column;align-items:center;justify-content:flex-start;
    gap:8px;min-height:0;width:auto;padding:10px 4px 9px;border-radius:14px;
    background:none;border:0;text-align:center;color:var(--text-2)}
  .m-hb:active{background:rgba(255,255,255,.05)}
  /* The rail's ring at tile size. HOW it fills — the track, the green sweep,
     the seam, the check settling into the middle — all lives in app.css and
     is shared; the only thing that differs here is the diameter.
     40px rather than 34, and the check inside it 20px rather than 14: the
     mark is what tells you it worked, and it was reading as a tick drawn for
     a 13px sidebar row because it was one. */
  .m-hb .hb-ring{width:40px;height:40px;flex:0 0 40px}
  .m-hb .hb-ring>svg.hr-svg{width:40px;height:40px}
  .m-hb .hr-track,.m-hb .hr-fill{stroke-width:2.9}
  .m-hb .hr-mark{width:20px;height:20px}
  .m-hb .hr-mark>svg{width:20px;height:20px}
  .m-hb .hr-count{font-size:12px}
  /* The rail keys the finished state off `.hb-row.is-done`, and on a phone
     the button IS the row. Same declarations, different ancestor. */
  .m-hb.is-done .hr-mark{opacity:1;transform:scale(1)}
  .m-hb.is-done{background:none}
  /* As the rail does it: the finished habit's name goes quiet. No strike —
     that came from the list layout's rule further up and the desktop has
     never had one; the ring is the signal. */
  .m-hb.is-done .m-hb-n{color:var(--muted);text-decoration:none}
  .m-hb-n{font-size:11.5px;line-height:1.3;font-weight:500;white-space:normal;
    display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;
    overflow:hidden;text-overflow:clip;min-width:0}
}

@media (prefers-reduced-motion:reduce){
  .hr-fill,.hr-mark{transition:none}
}

/* ── The calendar's view switcher, centred in its own track ──────────────
   The track is 38px tall with 3px of padding — 32px of room — and the tabs
   were given a 42px minimum for the finger. Ten pixels of tab hung out of
   the bottom of the container, which is exactly the "sitting too low" the
   review saw. The track grows to fit the target instead of the target
   overflowing the track. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .cal-modes{height:auto;padding:4px;align-items:stretch}
  .cal-modes [role="tab"]{height:auto;min-height:42px;display:grid;
    place-items:center;line-height:1}
  .cal-mode-pill{top:4px;bottom:4px;left:4px}
}

/* ── The sky, quieter up close ───────────────────────────────────────────
   Ambience on a 27-inch display is texture; the same marks 30cm from your
   face are specks. Fewer of them (see stars.js) and dimmer. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  #los-stars{opacity:.5}
}

/* ── The Diary page's margin stripe ──────────────────────────────────────
   A ruled page has a margin, and the stripe belongs IN it. The stripe was
   left at the desktop's 32px while the phone's page padding came down to
   15px, so the line ran straight through the first character of every line
   of writing. The margin comes back — narrower than a desktop's, wide enough
   to be one — and the stripe sits inside it. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .dia-left,.dia-right{padding:16px 16px 18px 34px}
  .dia-left::before,.dia-right::before{left:22px}
  .dia-ci-head,.dia-ci-group,.dia-rhythm{margin-left:0}

  /* The two pages are one book, joined deliberately: the seam is a fold, not
     two boxes touching. The lower page keeps the same radius as the upper
     one at its own outer corners and squares only where they meet, which is
     what a spread folded in half actually does. */
  .dia-book.bk-spread{border-radius:16px;overflow:hidden;
    box-shadow:var(--paper-shadow)}
  .dia-left{border-radius:16px 16px 0 0;box-shadow:none;
    border-bottom:1px solid rgba(255,255,255,.07)}
  .dia-right{border-radius:0 0 16px 16px;margin-top:0;border-top:0;
    background:
      linear-gradient(180deg,rgba(0,0,0,.22) 0,rgba(0,0,0,0) 26px),
      var(--paper-2);
    box-shadow:inset 0 9px 16px -14px rgba(0,0,0,.95)}
  /* The fold itself: a hairline of light on the lower page's top edge, the
     way the far side of a crease catches it. */
  .dia-right::after{content:'';position:absolute;top:0;left:0;right:0;height:1px;
    background:linear-gradient(90deg,
      rgba(255,255,255,0) 0%, rgba(255,255,255,.10) 18%,
      rgba(255,255,255,.10) 82%, rgba(255,255,255,0) 100%)}
}

/* ── A task's steps, as a nested block rather than a stray panel ─────────
   The panel was indented 31px on the left — the desktop's title column — and
   flush to the right, which on a 362px card reads as something that has come
   loose rather than as part of it. It gets a rule and an even inset, the
   controls get a thumb's worth of room, and the delete stops being visible
   only on hover, which on a phone means visible never. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  :root{--step-tick:22px}
  .t-steps{margin:10px 4px 2px 10px;padding-left:12px;
    border-left:1px solid var(--hairline);gap:1px}
  .ts-row{padding:5px 4px 5px 5px;min-height:40px;border-radius:9px}
  /* An <input> cannot wrap, so a long step is going to end somewhere: an
     ellipsis says "there is more" and a hard cut says the layout broke. */
  .ts-name{font-size:12.5px;padding:3px;text-overflow:ellipsis}
  .ts-del{color:var(--muted);width:28px;height:28px;font-size:16px}
  .ts-trail-spacer{width:28px}
  /* The labels align with the panel, not with the step text: a second indent
     inside an already-indented block reads as drift. */
  .ts-label{padding-left:2px}
  .ts-group{margin-top:9px}
  /* A dashed hairline ring is invisible against a tinted card at arm's
     length — the step reads as having no control at all. Same shape, enough
     contrast to be seen as "not yet" rather than as "missing". */
  .step-tick.is-locked{border-color:var(--border-strong);opacity:.85}
  /* Flush with the panel, not indented to the text column: a 32px step-in on
     a 317px row costs a tenth of the field and reads as misalignment. */
  .ts-more,.ts-add,.ts-ready,.ts-error{margin-left:0}
  .ts-more{min-height:34px;display:flex;align-items:center;
    text-decoration:none;padding:0 3px}
  .ts-add{margin-top:6px;gap:8px}
  .ts-add .m-input,.ts-add input{min-height:38px}
  .ts-add-btn{min-height:38px;padding:0 12px}
  .ts-label{font-size:9px}
  .tm-steps{min-height:26px}
}

/* ── The Book's cover ────────────────────────────────────────────────────
   64px of Playfair on a 340px page is two words per line and a title that
   pushes Open book off the bottom. The type scales with the page instead of
   being fixed, and the composition tightens: the cover is a cover, and the
   one action on it has to stay on it. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  /* The closed cover is one page of the open book — same width the reading
     page gets, so opening it does not change the size of the object in your
     hand. `height:100%` inside an auto-height frame collapsed it to 236px,
     which is a thumbnail, not a book. */
  .bk-cover-frame{aspect-ratio:auto;height:auto;width:100%}
  .bk-stage-cover .bk-book{aspect-ratio:210/297;width:100%;height:auto;
    max-width:none;max-height:64dvh;margin:0 auto}
  .bk-cover{padding:26px 22px 22px;gap:0}
  .bk-cover-mark{font-size:12px}
  .bk-cover-pre{font-size:9.5px;letter-spacing:.26em;margin-bottom:10px}
  /* Scales with the page, floors at something still book-like, and never
     grows past what two lines of it can occupy. */
  .bk-cover-title{font-size:clamp(26px,8.4vw,38px);line-height:1.1;
    overflow-wrap:anywhere}
  .bk-cover-sub{font-size:14px;margin-top:9px}
  .bk-cover-rule{width:96px;margin:18px 0}
  .bk-cover-author{font-size:9.5px;letter-spacing:.14em}
  .bk-open{margin-top:22px;min-height:44px;padding:0 22px}
  .bk-stage-cover{gap:0}
  .bk-stage-cover .bk-arrow-ghost{display:none}
}


/* ══════════════════════════════════════════════════════════════════════
   §9 CREATING SOMETHING IS A DIALOG, NOT A DRAWER
   A bottom sheet is right for a MENU — a short list you pick from and
   dismiss. The four creation flows are forms: a name, a date, a time, a
   repeat, a calendar, and a decision at the end. Attached to the bottom
   edge they read as a drawer somebody pulled up, they put the primary
   action within a thumb's width of the navigation, and the top of the form
   is the furthest thing from the eye.

   So they are centred cards on a phone, the way they are on a desktop:
   air on every side, the title at the top, the action at the foot of the
   CARD rather than the foot of the screen. Everything else — More, Quick
   add, the task actions, a Book's contents — stays a sheet, because those
   really are menus.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .modal.ev-modal,.modal-reminder,.modal-schedule{
    left:50%;right:auto;top:50%;bottom:auto;
    transform:translate(-50%,-50%);
    width:calc(100vw - 28px);max-width:460px;
    max-height:min(84dvh,720px);
    border-radius:18px;
    box-shadow:0 24px 64px -16px rgba(0,0,0,.72),0 0 0 1px rgba(255,255,255,.05)}
  /* The footer belongs to the card. It keeps the safe-area padding only
     when the card actually reaches the bottom of the screen, which it does
     not — so the extra inset would be a gap inside a dialog. */
  .modal.ev-modal .m-foot,.modal-reminder .m-foot,.modal-schedule .m-foot{
    position:static;padding-bottom:14px;border-radius:0 0 18px 18px}
  .modal.ev-modal .m-body,.modal-reminder .m-body,.modal-schedule .m-body{
    padding-bottom:12px}
  /* A dialog that is not attached to the bottom edge must not have its
     pickers attached to it either — they open where the control is, which
     is what `anchor()` writes as an inline position. Nothing here declares
     left or top, so that inline value is what applies. */
  .modal.ev-modal .cf-pop:not([hidden]),
  .modal-reminder .cf-pop:not([hidden]),
  .modal-schedule .cf-pop:not([hidden]){
    position:absolute;width:auto;max-height:min(58dvh,420px);
    border-radius:14px;z-index:210;overflow-y:auto;
    background:var(--surface);border:1px solid var(--border);
    box-shadow:0 18px 44px -12px rgba(0,0,0,.7)}
  /* The navigation steps aside for a dialog exactly as it does for a sheet. */
  body.modal-open .mnav{transform:translateY(110%)}
}

/* ── Two artefacts on a task that has steps ──────────────────────────────
   The blocked tick is a progress ring with its own 44px hit area on
   `::before`. The generic touch rule ALSO targets `.t-tick::after` — and
   `::after` is the ring's dark centre. The two collided and the centre was
   drawn at 44px: a black disc the size of the whole control, hanging off the
   left edge of any task with unfinished steps. The ring keeps its own
   geometry; the generic overlay does not apply to it. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .t-tick.is-blocked::after{width:auto;height:auto;inset:3px;
    top:auto;left:auto;transform:none}
  .t-tick.is-blocked{width:22px;height:22px}
  .t-tick.is-blocked::before{inset:-11px}

  /* Project and its badge travel together and wrap as one, so a separator is
     never left at the end of a line with nothing after it — and no dot joins
     the two groups, because the project's own colour and the badge beside it
     already say where one ends and the other begins. */
  .t-meta-ctx{display:inline-flex;align-items:center;gap:6px;min-width:0;
    max-width:100%;margin-left:2px}
}
.t-meta-ctx{display:inline-flex;align-items:center;gap:6px;min-width:0}

/* ── A pinboard's page arrows leave the canvas ───────────────────────────
   They are centred over the page everywhere else, which is right for a page
   of writing and wrong for a board: the whole surface pans, so a button
   floating in the middle of it is something you press while trying to move
   the board. They go to the foot of the page, inside the padding reserved
   for them, clear of the zoom cluster in the canvas's own corner. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .bk-stage:has(.bk-l-pinboard) .bk-arrow{top:auto;bottom:10px;transform:none;
    width:40px;height:40px}
  .bk-stage:has(.bk-l-pinboard) .bk-arrow:hover:not(:disabled){transform:none}
  .bk-stage:has(.bk-l-pinboard) #bk-prev{left:14px}
  .bk-stage:has(.bk-l-pinboard) #bk-next{right:14px}
  /* The board's own controls stay in the canvas, on the opposite side from
     nothing — the arrows are outside it now, so the corner is free. */
  .pin-ctl{right:8px;bottom:8px}
  .bk-l-pinboard .bk-page-title{font-size:15px}
  .bk-pin-tools .btn{min-height:36px;font-size:12.5px;padding:0 11px}
}

/* ══════════════════════════════════════════════════════════════════════
   RELATED, on a phone

   Same information, same order. The type moves ABOVE the title rather than
   beside it — a 62px label column on a 360px screen leaves the title about
   forty characters, and a truncated title is the one part of a link that has
   to survive.
   ══════════════════════════════════════════════════════════════════════ */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .rel{margin-top:16px;padding-top:12px}
  .rel-go{min-height:var(--m-tap);padding:8px;gap:8px;
    display:grid;grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:'kind rel' 'body body';align-items:center}
  .rel-kind{grid-area:kind;min-width:0}
  .rel-rel{grid-area:rel;max-width:none;text-align:right}
  .rel-body{grid-area:body}
  .rel-title{font-size:13.5px;white-space:normal;
    display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
  .rel-x{width:34px;height:34px;color:var(--muted)}
  .rel-row:active{background:rgba(255,255,255,.05)}
}

@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .rel-pick{width:calc(100vw - 24px);max-width:none}
  .rel-pick-body{min-height:0}
  .rel-pick-kind{flex-direction:column;align-items:stretch;gap:5px}
  .rel-pick-row{min-height:var(--m-tap)}
  .rel-pick-list{max-height:40dvh}
  .rel-add{min-height:32px;display:inline-flex;align-items:center}
}


/* ── What Life OS knows about you ────────────────────────────────────────
   A list of sentences, grouped, each one editable in place. Deliberately not
   a table of confidence scores: the person is here to check what is believed
   about them, not to audit how sure the machine is. */
.set-mem-h{padding-top:14px;padding-bottom:2px}
.set-mem-cat{font-size:10.5px;font-weight:700;letter-spacing:.09em;
  text-transform:uppercase;color:var(--muted)}
.set-mem .set-item-name{font-size:14px}

/* ── Memory at phone width ───────────────────────────────────────────────
   Three things have to hold, and one of them used to fail: the sentence must
   wrap in full (it is a textarea now, so it can), the controls must stay
   reachable, and the last row must not sit under the bottom bar. Actions go
   BENEATH the text rather than competing with it for one horizontal line —
   at 390px a row holding a sentence, Pin and Forget gives the sentence about
   forty characters. */
@media (max-width:899px),(max-height:500px) and (max-width:1099px){
  .set-mem,.set-item[data-cand]{flex-direction:row;flex-wrap:wrap;
    align-items:flex-start;row-gap:6px}
  .set-mem .set-item-dot,.set-item[data-cand] .set-item-dot{margin-top:9px}
  .set-mem .set-item-name,.set-item[data-cand] .set-cand-t{flex:1 1 100%;order:-1}
  /* The controls sit on their own line, left-aligned under the text, each
     still a full touch target. */
  .set-mem .set-item-go,.set-mem .set-item-x,
  .set-item[data-cand] .set-item-go,.set-item[data-cand] .set-item-x{
    min-height:var(--m-tap);display:inline-flex;align-items:center;
    padding-left:10px;padding-right:10px;margin-left:-10px}
  .set-mem .set-item-x,.set-item[data-cand] .set-item-x{margin-left:0}
  .set-cand-sub{white-space:normal;overflow-wrap:anywhere}
  /* Nothing here sets its own bottom padding. §21 puts the navigation inset on
     .main-wrap, once, and Settings scrolls inside it — a second one here would
     be a hand-tuned number competing with the one that already works. */
}
.set-cand-t{flex:1;font-size:14px;color:var(--text-2);min-width:0;
  display:flex;flex-direction:column;gap:3px}
/* What this would replace, when it contradicts something already believed.
   The sentence on its own reads as news; with this line it reads as a
   decision, which is what it is. */
.set-cand-sub{font-size:12px;line-height:1.35;color:var(--muted)}

/* What was asked for and could not be prepared. A note, not an error: the
   user said four things and got three cards, and something has to account
   for the fourth. */
.asst-note-line{margin:0;font-size:12.5px;color:var(--warn);line-height:1.45}
