/* The phone build.

   Loaded after styles.css, and almost every rule in here is inside a
   `max-width: 760px` or a `pointer: coarse` query - so on a laptop this file
   changes nothing at all. It is the same app; it is not a second app.

   Three ideas run through it:

     Reach.      Anything you press often moves to the bottom third of the
                 screen, and nothing you press is smaller than a fingertip.
                 44px is the floor, not the target.

     Show it.    A cursor can hover to find out what something is. A finger
                 cannot. Everything that was revealed on hover is either drawn
                 permanently or given a label.

     One column. Every side-by-side arrangement in the app was a decision made
                 for a wide window. On a phone they all stack, in the order
                 that answers "what now?" first. */

:root {
  /* How tall the bottom bar stands, including the strip of screen below it
     that the home indicator owns. Every screen's bottom padding is written
     against this, so the last row of a list is never sat under the bar. */
  --tabbar-h: 58px;
  --tabbar-total: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
  --tap: 44px;
}

/* ============================================================== the tab bar

   Four destinations and the capture button, pinned to the bottom edge. Frosted
   like the top bar for the same reason: the content scrolling underneath stays
   faintly visible, so the bar reads as a layer over the app rather than a lid
   clamped onto it. */

.tabbar { display: none; }

@media (max-width: 760px) {
  .tabbar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 60;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    align-items: stretch;
    height: var(--tabbar-total);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: color-mix(in srgb, var(--background) 86%, transparent);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-top: 1px solid var(--border);
  }

  .tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    border: 0;
    background: transparent;
    color: var(--subtle-fg);
    padding: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: color var(--dur) var(--ease);
  }
  .tab svg {
    width: 21px; height: 21px;
    fill: none; stroke: currentColor; stroke-width: 1.5;
    stroke-linecap: round; stroke-linejoin: round;
  }
  .tab-label { font-size: 10px; font-weight: 600; letter-spacing: .01em; }

  /* The active tab gets full ink and a short bar above it. Color alone would
     be doing the work of two signals and none of them well. */
  .tab.active { color: var(--foreground); }
  .tab.active .tab-icon { position: relative; }
  .tab.active .tab-icon::after {
    content: '';
    position: absolute; left: 50%; top: -9px;
    width: 16px; height: 2.5px; margin-left: -8px;
    border-radius: 2px;
    background: var(--primary);
  }
  /* Pressed state, since there is no hover to tell you the tap registered. */
  .tab:active { color: var(--foreground); transform: scale(.94); }

  /* Capture sits proud of the bar. It is the one control that is not a place
     you go but a thing you do, and it should not look like the others. */
  .tab-capture { position: relative; }
  .tab-plus {
    display: flex; align-items: center; justify-content: center;
    width: 46px; height: 46px;
    margin-top: -16px;
    border-radius: var(--radius-full);
    background: var(--primary);
    color: var(--primary-foreground);
    box-shadow: var(--shadow-md);
    border: 2.5px solid var(--background);
  }
  .tab-plus svg { width: 20px; height: 20px; stroke-width: 1.9; }
  .tab-capture:active .tab-plus { transform: scale(.92); }

  .tab-badge {
    position: absolute;
    top: -20px; right: 50%; margin-right: -26px;
    min-width: 19px; height: 19px; padding: 0 5px;
    border-radius: var(--radius-full);
    background: var(--destructive); color: var(--on-destructive, #fff);
    font-size: 11px; font-weight: 700; line-height: 19px; text-align: center;
    border: 2px solid var(--background);
  }
  .tab-badge[hidden] { display: none; }
  .tab-badge.stale { background: var(--warning); color: var(--neutral-900); }

  .toast-host { bottom: calc(var(--tabbar-total) + 12px); }

  /* Clear of the tab bar, and small enough not to sit on top of whatever the
     screen is showing. A bug found on a phone is still a bug worth hearing
     about, so it stays. */
  .bug-btn { bottom: calc(var(--tabbar-total) + 6px); right: 8px; opacity: .28; }
}

/* ============================================================== the frame

   A phone screen is a fixed frame, not a long page. The top bar and the bottom
   bar are pinned, and the screen scrolls between them - so the two bars stay
   exactly where your thumb left them however far down a list you are.

   The day and the month are the exceptions, and they do not scroll: they are
   shrunk until they fit, because seeing all of one at once is the entire point
   of drawing it as a grid. See frame.js.

   Everything about this is inside the phone query. On a laptop the page is
   still a page. */

@media (max-width: 760px) {
  html, body {
    height: 100%;
    overflow: hidden;
    overscroll-behavior: none;
  }

  main {
    position: fixed;
    left: 0; right: 0;
    top: var(--topbar-h);
    bottom: var(--tabbar-total);
    padding: 10px 14px 0;
    overflow: hidden;
  }
  @supports (padding: max(0px)) {
    main {
      padding-left: max(14px, env(safe-area-inset-left));
      padding-right: max(14px, env(safe-area-inset-right));
      padding-bottom: 0;
    }
  }

  /* Cards lose their frame. Inside a fixed screen the screen IS the card, and
     a page of content cut across a card's border looks like a mistake rather
     than a page. */
  .pane-body .card,
  .hub-body > * > .card,
  .hub-body > .card {
    background: transparent;
    border: 0;
    box-shadow: none;
    padding: 0;
    border-radius: 0;
  }
  .card + .card { margin-top: 14px; }

  /* ------------------------------------------------- the scrolling frame

     The screen is pinned to exactly the room between the two bars and scrolls
     inside it, so the bars stay still and only the content moves.

     This used to cut a screen into pages you dragged between, with dots down
     the edge and a More button. See frame.js for why that went: it only helped
     when the pages came out full, most screens did not, and what was left was
     one app with two different answers to what dragging upward means. */

  .in-frame {
    position: relative;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  /* A screen that fits does not get a scrollbar for the two pixels of rounding
     it misses by. See frame.js, which measures and adds this. */
  .in-frame.fits { overflow: hidden; }

  /* A note and five ways to file it, side by side, is a laptop shape. On a
     phone the five pills are 325px of fixed width against a 320px column, so
     the note beside them was squeezed to nothing - a word per line down the
     left edge, with the pills running off the right. They stack: read the
     note, then answer it. */
  .capture-row { flex-direction: column; align-items: stretch; gap: 8px; }

  /* The demo banner is a full paragraph and a button, which on a phone is a
     fifth of the screen given to something you already know. One line. */
  .demo-banner {
    padding: 7px 11px;
    font-size: 12px;
    margin-bottom: 8px;
    /* nowrap, or "one line" is a wish rather than a rule. Wrapping is decided
       before any item is shrunk, so with it left on the wide screen's `wrap`
       the button simply dropped to a row of its own and the shrinking below
       never happened. */
    display: flex; align-items: center; gap: 8px; flex-wrap: nowrap;
  }
  /* min-width, or none of the rest of this line does anything: a flex item
     will not shrink below its own content by default, and `nowrap` makes that
     content the whole sentence. So it never truncated - it pushed the button
     onto a second row and took the fifth of the screen this rule exists to
     stop it taking. */
  .demo-banner span {
    min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* "Example data." stays. It was hidden here to save room, which left the
     phone showing only "Click around freely - none of this is yours" - the
     reassurance without the fact it rests on. It is the first thing the line
     truncates to, so it is the one part guaranteed to be read. */
  .demo-banner .btn { flex: none; height: 30px; padding: 0 10px; font-size: 12px; }
}

/* ============================================================== the top bar

   With the destinations moved to the bottom, the top bar has one job: say who
   you are and what day it is, and hold the way out to settings. The pills go.
   The greeting comes back, because there is now room for it. */

@media (max-width: 760px) {
  /* Several things stick to the underside of the top bar and are positioned
     from this. The bar is shorter on a phone, so the number has to follow it
     or everything sticky sits a centimeter too low. */
  /* There is no top bar on a phone.
     It emptied out one button at a time - the four view buttons moved to the
     calendar's own row, capture and the microphone became the raised middle
     button on the bottom bar, and the gear is the last button on that bar now
     too. What was left was a 59px strip holding nothing, at the top of a screen
     he had already called cramped. Hidden rather than removed, because a
     laptop window dragged narrow and back again must not need a reload - and
     the menu still hangs itself off the button in here. */
  .topbar { display: none; }
  :root { --topbar-h: 0px; }

/* ========================================================= sideways gestures

   While a drag is being followed the screen moves with the finger and the name
   of where you are heading fades in at the edge you are pulling from. Without
   it a swipe is a guess that either works or does something surprising. */

/* The hint is positioned against the screen area, which the phone frame above
   already makes `position: fixed`. There is deliberately no `position` rule for
   it here: an earlier version set `main { position: relative }` at this point
   in the file, which - being later in source order than the frame - quietly
   un-fixed the frame and left the whole phone layout scrolling like a page.
   The hint only ever appears on a phone, so the frame is always its anchor. */

.swipe-hint {
  position: absolute;
  top: 50%; right: 6px;
  transform: translateY(-50%);
  z-index: 5;
  padding: 7px 12px;
  border-radius: var(--radius-full);
  background: var(--primary);
  color: var(--primary-foreground);
  font-size: 12px; font-weight: 650;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 90ms var(--ease);
  box-shadow: var(--shadow-md);
}
.swipe-hint.left { right: auto; left: 6px; }
.swipe-hint.showing { opacity: 1; }
.swipe-hint:empty { display: none; }

/* Sideways scrollers keep their own drags, and must not also hand the phone's
   back-swipe the gesture when they reach their end. */
.week-scroll, .planner-scroll, .year-chart, .subnav, .cal-modes {
  overscroll-behavior-x: contain;
}

/* ============================================================== swipe rows

   A task row you can throw sideways: complete it one way, deal with it later
   the other. The actions are drawn behind the row and revealed as it slides,
   so what is about to happen is visible before you let go. */

/* Square, not rounded. A task row is a line in a list, not a card - and a
   radius here clips the row's own white face at each corner, letting the
   tinted goal block behind show through as four little notches. */
.swipe-row {
  position: relative;
  overflow: hidden;
  touch-action: pan-y;
}
.swipe-face {
  position: relative;
  z-index: 1;
  background: var(--card-bg);
  transition: transform 190ms var(--ease);
  will-change: transform;
}
.swipe-row.swiping .swipe-face { box-shadow: var(--shadow-sm); }

.swipe-action {
  position: absolute;
  top: 0; bottom: 0;
  display: flex; align-items: center; gap: 7px;
  padding: 0 16px;
  font-size: 12.5px; font-weight: 650;
  color: #fff;
  width: 50%;
}
.swipe-left  { right: 0; justify-content: flex-end; text-align: right; }
.swipe-right { left: 0;  justify-content: flex-start; }
.swipe-action-icon { font-size: 15px; line-height: 1; }
.tone-done   { background: var(--success); }
.tone-danger { background: var(--destructive); }
.tone-move   { background: var(--info); }

/* Armed: past the point where letting go fires it. The panel brightens and the
   label steps up, which is the only warning you get and needs to be plain. */
.swipe-row.armed-left  .swipe-left,
.swipe-row.armed-right .swipe-right { filter: brightness(1.08); }
.swipe-row.armed-left  .swipe-left  .swipe-action-label,
.swipe-row.armed-right .swipe-right .swipe-action-label { font-weight: 750; }

/* There is no pull-to-refresh. Dragging down means the previous page now, and
   one gesture cannot mean two things. The calendar re-syncs on focus anyway. */

/* ============================================================ bottom sheets

   A dialog on a phone rises from the bottom edge, keeps a grip you can take
   hold of, and can be thrown back down. The middle of the screen is where a
   desktop puts a dialog because that is where the eye already is; on a phone
   it is the one place the hand cannot reach. */

.sheet-grip { display: none; }

@media (max-width: 760px) {
  .modal-backdrop {
    padding: 0;
    align-items: flex-end;
    /* Dims with the drag, so throwing the sheet away lightens the room with
       it rather than snapping at the end.

       Deliberately no transition here. A transition on `background` pins the
       color to the value it had when the transition was set up, and then does
       not notice that the custom property inside it has changed - so the whole
       drag would run against a backdrop that never moved. The dimming is
       already continuous because a finger is driving it; the only moment that
       wants easing is the spring back, which gets it below. */
    background: rgba(0, 0, 0, calc(.42 * var(--sheet-dim, 1)));
  }
  .modal-backdrop.settling { transition: background 180ms var(--ease); }

  .modal {
    width: 100%;
    max-width: none;
    max-height: 92dvh;
    border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
    padding: 4px 18px calc(18px + env(safe-area-inset-bottom, 0px));
    overflow-y: auto;
    overscroll-behavior: contain;
    animation: sheet-up 220ms var(--ease);
    transition: transform 210ms var(--ease);
    will-change: transform;
    box-shadow: 0 -8px 30px -6px rgba(0, 0, 0, .18);
  }

  @keyframes sheet-up {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
  }

  /* The grip. Wide enough to grab without aiming, and it stays put while the
     sheet's content scrolls under it. */
  .sheet-grip {
    display: block;
    position: sticky;
    top: 0;
    z-index: 2;
    margin: 0 -18px 6px;
    padding: 11px 0 9px;
    background: var(--popover);
    cursor: grab;
    touch-action: none;
  }
  .sheet-grip::before {
    content: '';
    display: block;
    width: 38px; height: 4px;
    margin: 0 auto;
    border-radius: 2px;
    background: var(--border-strong);
  }

  .modal h2 { font-size: 18px; }

  /* Actions go to the foot of the sheet, full width, in reach. Confirm on the
     right stays on the right, so muscle memory carries over from the desktop.

     Scoped to `.modal` on purpose. `.modal-actions` is reused for ordinary
     button rows inside cards - the setup buttons on the settings screen, the
     Buffer and Wrap-up pair under the day - and unscoped these rules pinned
     those to the bottom of the screen with a rule across the top and margins
     hanging off both edges. */
  .modal .modal-actions {
    position: sticky;
    bottom: 0;
    margin: 14px -18px 0;
    padding: 12px 18px calc(4px + env(safe-area-inset-bottom, 0px));
    background: var(--popover);
    border-top: 1px solid var(--border);
    display: flex;
    gap: 9px;
  }
  .modal .modal-actions .btn { flex: 1; height: var(--tap); }

  /* And in a card, the same row simply wraps rather than running off the
     edge - two full-width buttons will not sit side by side on a phone. */
  .card .modal-actions { flex-wrap: wrap; gap: 8px; }
  .card .modal-actions .btn { flex: 1 1 auto; }

  body.scroll-locked {
    position: fixed;
    width: 100%;
    overflow: hidden;
  }
}

/* ================================================================ hub tabs

   Seven tabs in the planning hub used to wrap onto two rows, which left the
   underline running along the bottom of the second row and an active tab on
   the first row underlined against nothing. One row that scrolls instead: the
   underline stays one continuous rule, and the tab you are on is scrolled into
   the middle after every render. */

@media (max-width: 760px) {
  .subnav {
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    margin-left: -14px; margin-right: -14px;
    padding-left: 14px; padding-right: 14px;
  }
  .subnav::-webkit-scrollbar { display: none; }
  .subtab {
    flex: none;
    scroll-snap-align: center;
    padding: 12px 14px 13px;
    min-height: var(--tap);
    white-space: nowrap;
  }
}

/* ============================================================ touch targets

   The floor is 44px. Everything below is a control that was sized for a mouse
   and never revisited. */

@media (pointer: coarse) {
  .btn      { min-height: var(--tap); }
  .btn-sm   { min-height: 38px; padding: 0 14px; }
  .btn-xs   { min-height: 34px; padding: 0 11px; }
  .btn-ghost{ min-height: var(--tap); min-width: 38px; padding: 0 11px; }
  .pill     { min-height: 38px; }
  .pill-sm  { min-height: 34px; padding: 7px 13px; font-size: 12.5px; }
  .linkish  { padding: 6px 0; display: inline-block; }
  .add-task { min-height: 38px; }

  /* The two checkboxes that were still mouse-sized. A missed tick on a
     reading plan is a tick you then have to hunt for. */
  .task-row .checkbox { width: 24px; height: 24px; }
  .reading-tick { width: 24px; height: 24px; }
  .reading-check { padding: 9px 0; min-height: var(--tap); }
  .reading-check input, .check-field input { width: 22px; height: 22px; }

  /* Calendar chrome. */
  .cal-arrow { width: 40px; height: 40px; }
  .cal-mode  { min-height: 36px; padding: 0 13px; }
  /* No min-height on a week slot. An hour is exactly `--hour` tall and the
     labels, the blocks and the now line are all positioned off that number -
     a slot forced taller than it puts every one of them out by the
     difference, further with each hour down the page. */

  /* Reordering. The rows are dragged with HTML5 drag-and-drop, which a finger
     never starts - so on touch the arrows are the only way, and they are sized
     and labeled as the real control rather than a fallback. */
  .rank-grip { display: none; }
  .rank-actions .btn-ghost { min-width: var(--tap); font-size: 15px; }

  /* Everything the app used to reveal on hover. A finger never hovers, so
     these were invisible controls. */
  /* There is no hover on a phone, so it is either on or it does not exist.
     It was on at a third of a color that was already the palest one there is. */
  .hour-hit::after { opacity: .55 !important; }
  .week-dayhead.clickable { text-decoration: underline; text-underline-offset: 3px; }
  .rank-row, .choice-row, .month-cell { -webkit-tap-highlight-color: rgba(0, 0, 0, .06); }
  .flat-input {
    min-height: 38px;
    /* Borderless inputs read as text until you hover them. On touch they get a
       permanent hairline, or nobody knows they can be typed into. */
    border-bottom: 1px solid var(--input);
    border-radius: 0;
  }
  .window-row input[type="time"] { height: 38px; }
}

/* ============================================================ forms on phone

   iOS zooms the whole page when you focus an input under 16px, and it does not
   zoom back out. That single rule is the most disorienting thing a web app can
   do on a phone, and `datetime-local` had been missed from the list. */

@media (max-width: 760px) {
  input[type="datetime-local"], input[type="time"], input[type="date"] {
    font-size: 16px;
    width: 100%;
    height: var(--tap);
  }
  .window-row input[type="time"] { width: auto; min-width: 108px; }

  /* Two date fields side by side is ~160px each. They stack. */
  .field-row { flex-direction: column; gap: 10px; }

  /* The season screen sets this grid inline, so it takes an `!important` to
     unpick - the alternative is threading a screen-size check through the
     render code, which puts layout back in the JavaScript. */
  .season-split { grid-template-columns: 1fr !important; }
  .dump-pane { max-height: 220px; }

  /* Headers that pack a title, a filter row and two buttons onto one line. */
  .card-head { flex-wrap: wrap; gap: 8px; }
  .card-head h1 { flex: 1 1 100%; }
  .card-head .navpills { overflow-x: auto; scrollbar-width: none; max-width: 100%; }
  .card-head .navpills::-webkit-scrollbar { display: none; }

  /* Rows that squeeze their buttons out of the way when a title is long. */
  .sermon-actions, .person-row, .shelf-row, .review-line, .rank-row,
  .contact-line, .bed-line, .check-row, .span-row { flex-wrap: wrap; }
  .bed-line .bed-time { width: auto; flex: 1 1 auto; }

  /* Real tables. Five columns of rhythm and four of delta do not fit, so each
     row becomes a small stack with its heading beside each value. */
  .rhythm-table, .rhythm-table tbody, .rhythm-table tr, .rhythm-table td { display: block; width: auto; }
  .rhythm-table thead { display: none; }
  .rhythm-table tr {
    padding: 11px 0;
    border-bottom: 1px solid var(--line);
  }
  .rhythm-table td { padding: 4px 0; }
  .rhythm-table td:first-child { width: auto; font-weight: 650; }
  .rhythm-table td[data-label]::before {
    content: attr(data-label);
    display: inline-block;
    width: 92px;
    color: var(--muted-fg);
    font-size: 12px;
  }

  .delta-table { font-size: 13px; }
  .delta-table th, .delta-table td { padding: 7px 4px; }

  /* The remainder strip and the week's day heads both stick to the top bar.
     Only one of them can be the thing under it. */
  .remainder-bar { top: calc(var(--topbar-h) - 12px); }

  .wizard { max-width: none !important; }
  .step-strip { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; }
  .step-strip::-webkit-scrollbar { display: none; }
  .step-chip { flex: none; min-height: 38px; }
}

/* ============================================================ today, phone

   Today is the screen the phone is built around, and on a phone its three
   columns become three panes you swipe between rather than three cards stacked
   into one very long scroll. Stacked, the goals were a screen below the day and
   the week's arithmetic a screen below that, which is to say neither was ever
   read. Side by side they are one drag apart, the same as they are on a laptop.

   The switch above them is not decoration. A gesture nobody knows about is a
   gesture nobody uses, so the panes are visibly tabs first and swipeable
   second. */

@media (max-width: 760px) {
  .pane-switch {
    display: flex;
    gap: 2px;
    margin-bottom: 12px;
    padding: 3px;
    background: var(--secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    position: sticky;
    top: calc(var(--topbar-h) + 4px);
    z-index: 20;
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
  }
  .pane-tab {
    flex: 1;
    min-height: 38px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--muted-fg);
    font-weight: 550;
    font-size: 13.5px;
    cursor: pointer;
  }
  .pane-tab.active {
    background: var(--card-bg);
    color: var(--foreground);
    font-weight: 650;
    border-color: var(--border);
    box-shadow: var(--shadow-xs);
  }

  /* The same three states the four buttons at the top of a wide screen have:
     the pane you are on is a solid pill, and the one you are touching gets a
     half-strength version of the same pill.

     A phone has no pointer, so there is no hover to answer - the press is
     where it shows. The hover is added back underneath for a laptop window
     dragged narrow enough to get this layout, where there is a mouse. */
  .pane-tab { transition: background var(--dur) var(--ease), color var(--dur) var(--ease); }
  .pane-tab:not(.active):active {
    background: color-mix(in srgb, var(--card-bg) 55%, transparent);
    color: var(--foreground);
  }
  @media (hover: hover) {
    .pane-tab:not(.active):hover {
      background: color-mix(in srgb, var(--card-bg) 55%, transparent);
      color: var(--foreground);
    }
  }

  /* The day's header: which day it is on the left, and on the right the way
     back to today and the way out to the week.

     There were two arrows here, one either side of the date, and moving five
     days meant pressing one of them five times. The seven days underneath do
     that job in one tap and say where in the week you are while they are at
     it, which no pair of arrows can. */
  .phone-day-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin: 0 0 2px;
  }
  .phone-day-head h2 { font-size: 17px; }
  .phone-day-head .day-head-left {
    flex: 1; min-width: 0;
    display: flex; align-items: baseline; gap: 7px;
  }
  .phone-day-head .day-date-beside { white-space: nowrap; }
  .phone-day-head .day-nav { flex: none; display: flex; align-items: center; gap: 6px; }

  /* Room for a thumb on each of the seven, and the label goes: at this width
     "Week" beside the icon costs the row more than it earns. */
  .day-chip { padding: 6px 0 5px; }
  .day-chip-num { width: 30px; height: 30px; font-size: 15px; }
  .cal-out { width: 40px; padding: 0; justify-content: center; }
  .cal-out-label { display: none; }

  /* The stacked fallback, still used anywhere the panes are not in play. */
  .three-pane {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  .three-pane > *:nth-child(2) { order: 1; }  /* the day */
  .three-pane > *:nth-child(1) { order: 2; }  /* goals and targets */
  .three-pane > *:nth-child(3) { order: 3; }  /* reading, then capacity */

  /* Collapsing a pane turns its title sideways, which is a sensible thing to
     do to a column and a nonsense thing to do to a full-width card. */
  .pane-collapsed { display: none; }

  /* The hour column was 46px of a 390px screen, and the block titles were
     being cut mid-word to pay for it. */
  .hour-label { width: 34px; font-size: 10.5px; }
  .hour-hit, .blocks-layer { left: 42px; }
  .block-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* The stacked day header, still used on any screen that is not the day
     itself - a wrapped heading with its controls on a row of their own. The
     day's own header does not wrap any more: it is a heading and two small
     buttons, and the seven days sit under it. */
  .day-head:not(.phone-day-head) { flex-wrap: wrap; gap: 8px; }
  .day-head:not(.phone-day-head) .day-nav { width: 100%; justify-content: space-between; }
  .day-head:not(.phone-day-head) .day-nav .btn-sm { flex: 1; }
  .day-nav .quiet-note { display: none; }
}

/* ============================================================ calendar, phone

   The bar carries a title, three navigation controls, a four-way switch and an
   Add button - about 400px of content in 326px of screen. It becomes two rows:
   the range and Add on top, then the switch and the arrows underneath. */

@media (max-width: 760px) {
  .cal-bar {
    flex-wrap: wrap;
    gap: 9px;
    padding: 11px 12px;
  }
  /* One row: which range, the three date buttons, then Add and Export.
     It used to wrap onto two, because the view tabs were down here as well
     and there is no fitting a heading, three tabs, three date buttons and two
     round ones across 375px. The tabs are across the top of the screen now,
     which left a row with room to spare. */
  .cal-title { flex: 1 1 100%; font-size: 17px; min-width: 0; order: 1; }
  /* `flex: none`, not `1 1 auto`: growing to fill the row is what pushed Add
     and Export onto a third line of their own. */
  .cal-nav { order: 2; flex: none; display: flex; align-items: center; gap: 6px; }
  .cal-right { order: 3; flex: none; margin-left: auto; display: flex; gap: 7px; }
  .cal-add, .cal-export { flex: none; width: 42px; padding: 0; font-size: 16px; }

  /* The grid fits the screen whatever it is holding - one day or a fortnight.
     It used to be pinned at 700px and scrolled, which was right when it only
     ever held a calendar week. Now that pulling the day wider is a gesture,
     a fixed width per day would have meant widening it showed you less of it.
     The width is set on the element itself; see grid() in week.js. */
  .week-dayhead { top: 0; }

  /* Month cells were 42px squares with a 5px bar and a 7px dot fighting for
     the same room. Taller, and the bars get to be legible.

     `--cell` is set by the pager once it knows how much room the month has, so
     the six weeks of a long month and the five of a short one both fill the
     screen exactly rather than one of them overflowing it. */
  .month-cell { min-height: var(--cell, 74px); padding: 5px 4px; }
  .month-daynum { font-size: 12px; }
  .month-bars { gap: 2px; }
  .month-dow { font-size: 10px; }

  /* The year planner is a 900px grid of 17px cells. On a phone it stays put
     behind the print button and the screen shows the months as a list. */
  .planner-scroll { border-radius: var(--radius-sm); }
  .planner-cell { min-height: 22px; }

  .cal-split { display: block; }
  .cal-rail { margin-top: 12px; }
}

/* ====================================================== the peek, on touch

   On a laptop the peek panel appears when the pointer rests on a day, and it
   is the only way to see what is in one. A finger cannot rest on anything, so
   on touch the same panel opens on a tap and closes like a sheet. */

@media (pointer: coarse) {
  .peek {
    position: fixed !important;
    left: 0 !important; right: 0 !important;
    top: auto !important;
    bottom: 0 !important;
    width: 100% !important;
    max-width: none !important;
    max-height: 70dvh;
    overflow-y: auto;
    border-radius: var(--radius-2xl) var(--radius-2xl) 0 0;
    padding-bottom: calc(14px + var(--tabbar-total));
    box-shadow: 0 -8px 30px -6px rgba(0, 0, 0, .18);
    animation: sheet-up 200ms var(--ease);
  }
  .peek-row { min-height: var(--tap); }
}

/* ======================================================= landscape and print

   Turned sideways a phone has almost no height, so the bar and the top bar
   between them would take a third of it. The bar shrinks and drops its
   labels. */

@media (max-width: 900px) and (orientation: landscape) and (max-height: 460px) {
  /* Only the bottom bar is left to shrink. */
  :root { --tabbar-h: 46px; }
  .tab-label { display: none; }
  .tab-plus { width: 38px; height: 38px; margin-top: -10px; }
}

@media print {
  .tabbar, .swipe-hint { display: none !important; }

  /* The fixed frame is a screen idea. On paper the page runs as long as it
     needs to, so the frame gives up its pinned height - otherwise printing
     from a phone would print one screenful and stop. */
  html, body { height: auto; overflow: visible; }
  main { position: static; overflow: visible; }
  .in-frame { height: auto !important; overflow: visible; }
}

/* ================================================================== export

   The agenda sheet: a day at a time, what is on it, and how long it runs. A
   month grid shrunk onto A4 is unreadable and a week grid is worse - a list is
   what people actually carry around. It is built only while printing and
   thrown away afterward, so it never exists on screen. */

.print-agenda { display: none; }

/* The chosen range has to look chosen. Without this the four options sit there
   identically and the sheet gives no sign it heard the tap. */
.choice-row.picked {
  border-color: var(--primary);
  box-shadow: 0 0 0 1px var(--primary);
  background: var(--card-bg);
}

/* `label.field` is `display: block`, which beats the browser's own rule for
   the hidden attribute - so the day count stayed on screen for every range,
   including the three that do not have one. */
.export-days[hidden] { display: none; }

@media print {
  body.printing-agenda > *:not(.print-agenda) { display: none !important; }
  body.printing-agenda .print-agenda { display: block; }

  .print-agenda { font-size: 11pt; color: #000; }
  .agenda-title {
    font-size: 15pt;
    margin: 0 0 14px;
    padding-bottom: 6px;
    border-bottom: 1.5px solid #000;
  }
  .agenda-day {
    /* Never split a day across two sheets - being able to look at one day and
       see all of it is the entire point of the format. */
    break-inside: avoid;
    page-break-inside: avoid;
    margin-bottom: 13px;
  }
  .agenda-day h2 {
    font-size: 11.5pt;
    margin: 0 0 5px;
    padding-bottom: 2px;
    border-bottom: 0.5px solid #999;
  }
  .agenda-list { list-style: none; margin: 0; padding: 0; }
  .agenda-list li { display: flex; gap: 10px; align-items: baseline; padding: 2px 0; }
  .agenda-time { width: 58px; flex: none; font-weight: 700; font-variant-numeric: tabular-nums; }
  .agenda-what { flex: 1; }
  .agenda-len { color: #555; font-variant-numeric: tabular-nums; }
  .agenda-empty { margin: 0; color: #777; font-style: italic; }
}

/* ============================================================ hold to talk

   The middle button of the tab bar records for as long as it is held. See
   public/js/voice.js. */

@media (max-width: 760px) {
  /* Without these, holding the button down on iOS brings up the magnifier and
     the copy menu instead of the microphone. */
  .tab-capture {
    touch-action: manipulation;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }

  .tab-capture.recording .tab-plus {
    background: var(--destructive);
    transform: scale(1.06);
  }
  .tab-capture.recording .tab-plus svg { opacity: .9; }
}

/* The bar that rises over the tab bar while a thumb is down.
   Deliberately not a sheet: nothing should cover the screen while somebody is
   still talking, because the whole point of holding the button is that you
   are already halfway out of the door. */
.talk-pill {
  position: fixed;
  left: 50%; bottom: calc(var(--tabbar-total) + 14px);
  z-index: 70;
  display: flex; align-items: center; gap: 9px;
  padding: 9px 16px;
  border-radius: var(--radius-full);
  background: var(--primary); color: var(--primary-foreground);
  box-shadow: var(--shadow-lg, var(--shadow-md));
  font-size: 13px; font-weight: 600;
  white-space: nowrap;
  transform: translate(-50%, 10px);
  opacity: 0;
  transition: opacity .18s ease, transform .18s ease;
  pointer-events: none;
}
.talk-pill.showing { opacity: 1; transform: translate(-50%, 0); }

.talk-dot {
  width: 9px; height: 9px; flex: none;
  border-radius: var(--radius-full);
  background: var(--destructive);
  /* Scaled from the live audio level rather than animated on a timer, so it
     is telling you something true. */
  transition: transform .06s linear;
}

.talk-time { font-variant-numeric: tabular-nums; opacity: .7; }
