@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap');
:root{
  --bone:#FAF9F5;
  --ink:#21201C;
  --muted:#6B6760;
  --faint:#9A958C;
  --hair:#E9E4DC;
  --hair-2:#F0ECE4;
  --card:#FFFFFF;
  --paper-tint:#FAFAF9;
  --grey-bar:#F1F0EE;
  --berry:#8b3558;
  --berry-soft:#F6E7EE;
  --berry-deep:#6e2940;
  --ok:#3E7D5A;
  --ok-soft:#E6F1EA;
  --warn:#B97A12;
  --warn-soft:#F7EEDD;
  --hold:#8A6D2F;
  --ground:#ECE9E3;
  --radius:14px;
  --radius-sm:10px;
  --shadow-lg:0 4px 14px rgba(33,32,28,.08), 0 1px 3px rgba(33,32,28,.06);
}
*{box-sizing:border-box;}
/* One base size, in a relative unit, for the whole app (David, 7 Sep 2026:
   "the text looks tiny, full stop" — plan.md §Future/soon). Every font-size
   below is now written in rem, i.e. as a fraction of THIS number, so a
   hardcoded px value can no longer sit deaf to two things at once: the
   reader's own browser text-size setting (px ignores it outright; rem
   doesn't), and a single change here (see the min-width media query at the
   foot of this file, which is the one place that changes it). 100% is the
   browser default (usually 16px) and is deliberately what the phone keeps —
   the phone's own reading of every rem value below must come out
   byte-identical to the px numbers it replaces. */
html{font-size:100%;}
html,body{margin:0;background:#fff;color:var(--ink);
  font-family:"Plus Jakarta Sans",-apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;line-height:1.5;height:100%;}

.app{background:var(--bone);display:flex;height:100vh;}

.side{flex:0 0 244px;background:#fff;border-right:1px solid var(--hair);display:flex;flex-direction:column;
  padding-top:0;}
.brand{padding:16px 18px 14px;border-bottom:1px solid var(--hair-2);}
/* Her real signature logo, one asset (David, 25 Aug 2026: "either it's
   missing or we use the logo"). It is now on SIGN-IN AND THE CLIENT-FACING
   SHELLS ONLY. The `.side .brand img.brandmark{max-width:190px}` rule that
   sized it for the 244px sidebar is gone with the <img> itself: evening
   ruling 25 took the script logo off Emese's own signed-in chrome ("This
   isn't the right logo, and I think we could actually lose it here"), so
   there is no mark in the sidebar left to size. A visitor still meets her
   practice; she does not need introducing to her own back office. */
.brand img.brandmark{display:block;width:100%;height:auto;}
.bk-card .brand img.brandmark,
.signin-card .brand img.brandmark{max-width:220px;margin:0 auto;}
/* The chrome wordmark (A5, overnight brief 5-6 Sep 2026; evening sign-off
   walk, ruling 6): "PRACTICE DESK is the chrome wordmark on every Emese
   screen, centred, 11px." Clients keep SEXTHERAPIST.LONDON — she knows
   the name of her own practice — so this never appears on a client page. */
.wordmark{text-align:center;font-family:'Montserrat',sans-serif;
  font-weight:500;font-size:0.6875rem;letter-spacing:.12em;color:#8B5A7D;
  text-transform:uppercase;margin-top:6px;}
/* Sign-in carries the name under the logo, block centred with a slight
   upward lift (the evening walk's E1 drawing). */
.wordmark-lift{margin-top:-2px;}
/* THE CHROME WORDMARK, one step larger (evening ruling 25). It is the only
   mark in the signed-in sidebar now, so it carries the weight the logo used
   to: 13px against the 11px it wears everywhere else, and no top margin
   because there is nothing above it to clear. */
.wordmark-chrome{font-size:0.8125rem;margin-top:0;}
.side .compose{margin:8px 14px 4px;}
/* Was an inline style on the form; lifted to a class so the narrow-screen
   block below can restate it without !important (piece #21). */
.sidesearch{padding:10px 14px 0;}
.navgrp{padding:8px 10px 4px;}
.navgrp .lbl{font-size:0.625rem;text-transform:uppercase;letter-spacing:.12em;color:var(--faint);
  font-weight:700;padding:8px 8px 6px;}
.nav{display:flex;align-items:center;gap:11px;padding:9px 10px;border-radius:10px;font-size:0.875rem;
  font-weight:500;color:var(--ink);margin-bottom:1px;cursor:default;text-decoration:none;}
a.nav{cursor:pointer;}
a.nav:hover{background:var(--hair-2);}
.nav .ct{margin-left:auto;font-size:0.6875rem;font-weight:700;background:var(--berry-soft);color:var(--berry-deep);
  border-radius:999px;padding:1px 7px;}
.nav .ct.warn{background:var(--warn-soft);color:var(--warn);}
.nav.on{background:var(--berry-soft);color:var(--berry-deep);font-weight:700;}
.side .divider{height:1px;background:var(--hair-2);margin:8px 16px;}
.side .foot{margin-top:auto;padding:12px 14px 16px;border-top:1px solid var(--hair-2);}
/* The `.av` initials chip that sat here is gone with the "ET" span it styled
   (struck on the 5 Sep walk; night brief 1 item 8). `.who` stays: her name
   and "Signed in" are the useful half. */
.side .foot .who{display:flex;align-items:center;gap:10px;font-size:0.8125rem;}
.side .foot .so{font-size:0.75rem;color:var(--faint);margin-top:10px;cursor:pointer;text-decoration:none;
  display:block;background:none;border:none;padding:0;width:100%;text-align:left;font-family:inherit;}

/* .contentwrap exists so a flash banner can sit ABOVE the panes instead of
   beside them. .content is a flex ROW (the panes sit side by side), so the
   .flashes div base.html injects became a sibling flex item and took a column
   of its own, shoving the whole page right — on every one of the app's flash
   messages, desktop only, worst of all on "did that save?". Mobile never had
   the fault because .content becomes a column at narrow widths. Keeping
   .content itself unchanged means every descendant selector still applies.
   (24 Aug 2026 dress rehearsal.) */
.contentwrap{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--bone);}
.content{flex:1;min-height:0;display:flex;min-width:0;padding-top:0;background:var(--bone);}
.detail{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--bone);}
.panehead{padding:16px 26px 12px;border-bottom:1px solid var(--hair);flex:0 0 auto;background:var(--bone);}
.panehead .h{font-size:1.1875rem;font-weight:700;letter-spacing:-.01em;display:flex;align-items:center;gap:10px;}
.panehead .sub{font-size:0.8125rem;color:var(--muted);margin-top:3px;}
.detailscroll{flex:1;overflow:auto;padding:22px 26px;}

.tag{font-size:0.625rem;font-weight:700;padding:2px 8px;border-radius:999px;display:inline-block;letter-spacing:.02em;}
.tag.client{background:var(--berry-soft);color:var(--berry-deep);}
/* Failure state (I28, piece #11): a failed refund badges "berry" in both the
   Money list row and the detail pane, and had no .tag rule at all, so it
   rendered as an unstyled badge. Solid berry on white rather than the usual
   soft/deep pairing, because the soft wash is already spoken for in the same
   Money list by .tag.client (Credit, Block) — a failure must not read as one
   of those. Palette's own berry, as .btn already pairs it with white; no new
   colour introduced. */
.tag.berry{background:var(--berry);color:#fff;}
.tag.ok{background:var(--ok-soft);color:var(--ok);}
.tag.nudge{background:var(--berry-soft);color:var(--berry-deep);}
.tag.warn{background:var(--warn-soft);color:var(--warn);}
.tag.enq{background:#EAEFF6;color:#3A5680;}
/* Reply and Enquiry were the same blue, and every row on the list wore one
   or the other. David, 7 Sep 2026: "These all at the same colour rather
   destroys the point of having a pill at all." Reply is now its own
   colour, distinct from Enquiry's blue, from the berry a client badge
   uses, from the amber of anything wanting attention and from the green of
   something already settled. */
.tag.reply{background:#E2EFED;color:#2C625E;}
.tag.pers{background:#F0ECE4;color:#7A746A;}
.tag.hold{background:#F4EEDD;color:var(--hold);}
.tag.prof{background:#EFEAF6;color:#5A3A80;}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;background:var(--berry);color:#fff;
  border:none;border-radius:10px;padding:10px 16px;font-size:0.875rem;font-weight:600;cursor:pointer;
  font-family:inherit;text-decoration:none;}
.btn.ghost{background:transparent;color:var(--berry);border:none;font-weight:600;padding:8px 0;cursor:pointer;}
.btn.sec{background:var(--card);color:var(--ink);border:1px solid var(--hair);}
.btn.block{display:flex;width:100%;}

/* ---------- list + detail (To do) ---------- */
.listpane{flex:0 0 360px;border-right:1px solid var(--hair);display:flex;flex-direction:column;min-width:0;}
.listscroll{flex:1;overflow:auto;padding:8px 0;}
.qrow{display:flex;gap:11px;align-items:flex-start;padding:11px 16px;border-bottom:1px solid var(--hair-2);
  cursor:pointer;position:relative;text-decoration:none;color:inherit;}
.qrow:hover{background:#fff;}
.qrow.sel{background:#fff;box-shadow:inset 3px 0 0 var(--berry);}
/* 6 Sep 2026, David: "can we fix the justification here?" — the tag column
   sets the text's left edge, and the tags are different widths (Clash,
   Approve, Enquiry, Nudge, Decide, Confirm), so every door's title started
   somewhere slightly different and the list read as ragged. A fixed column
   aligns every title and sub-line down one edge; the tag itself still sizes
   to its own text inside it. */
.qrow .typ{flex:0 0 74px;text-align:center;}
.qrow .body{flex:1;min-width:0;}
.qrow .body .t{font-size:0.875rem;font-weight:600;display:flex;align-items:center;gap:7px;}
.qrow .body .s{font-size:0.8125rem;color:var(--muted);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.qrow .when{flex:0 0 auto;font-size:0.75rem;color:var(--faint);font-variant-numeric:tabular-nums;white-space:nowrap;padding-top:1px;}
.qrow .marks{flex:0 0 auto;display:flex;align-items:center;gap:6px;color:var(--faint);font-size:0.75rem;}

.draftbox{background:#fff;border:1px solid var(--hair);border-radius:12px;padding:0;overflow:hidden;margin-bottom:14px;}
.draftbox .dh{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 12px;
  justify-content:space-between;padding:11px 15px;
  border-bottom:1px solid var(--hair-2);background:var(--berry-soft);}
/* A long label used to wrap under a hint sitting beside it, and the two
   interleaved into a block of text neither of them was ("These boxes will
   look a bit weird", David, 7 Sep 2026, on the two enquiry choice boxes in a
   narrow column). The label keeps the line it is on; the hint drops whole
   onto the next one rather than braiding into it. */
.draftbox .dh .l{flex:1 1 auto;min-width:0;}
.draftbox .dh .r{flex:0 1 auto;text-align:right;}
.draftbox .dh .l{font-size:0.6875rem;text-transform:uppercase;letter-spacing:.07em;color:var(--berry-deep);font-weight:700;}
.draftbox .dh .r{font-size:0.75rem;color:var(--berry-deep);}
.draftbox .body{padding:16px;font-size:0.875rem;line-height:1.7;white-space:pre-line;}

/* ---------- A client's message: the same box as her reply ---------- */
/* David, 7 Sep 2026, looking at the reply screen: "Can we have another box
   with a heading like this? Maybe purple at the top... But a white box. Maybe
   very, very slightly gray", then "their message as the title", then "the box
   for their message the same width as the box for Emese's reply." So an
   incoming message is now a `.draftbox theirs` — the same shape, the same
   berry header, the same full width as the box underneath it. It used to be a
   narrow chat bubble on --hair-2, the warm beige he did not want ("I am not
   fond of the brown background"). */
.draftbox.theirs{background:var(--paper-tint);}
.draftbox.theirs .body{white-space:normal;}
/* A long incoming message must not eat the screen. David, 7 Sep 2026:
   "Their message sometimes gets half the screen and she's still left with
   a[ few] lines for her message." So theirs scrolls inside its own box
   past a third of the window, and hers opens tall enough to write in. */
.draftbox.theirs .body{max-height:34vh;overflow-y:auto;}

/* The grey bar under a quoting message, and its "See full thread" button.
   David, 7 Sep 2026: "only show their most recent message, full stop, with a
   gray bar at the bottom explaining the context", "with a button to expand to
   see the full thread... see full thread". A <details> so the reveal needs no
   script and no form — the box sits outside her reply form and a real button
   in there would submit it. */
/* THE BUTTON GOES UNDERNEATH THE LINE, not beside it. David, 7 Sep 2026:
   "This box is annoying badly rendered or two attempts overlapping each
   other", then "I prefer the button underneath." Side by side, the white
   pill stood taller than the bar it sat in and the bar's own rounded ends
   showed through on either side of it, which read as two overlapping
   boxes. Stacked, the bar is a bar and the button is a button. */
/* THE SUMMARY IS NOT THE FLEX BOX (8 Sep 2026). It carried
   display:flex/column itself, and Safari does not honour that on a <summary>:
   the children stop being flex items, so the white button rendered ON TOP of
   the grey line rather than under it — "You have to fix this", with a
   screenshot of the pill covering the words. The summary now only paints the
   bar; `.qsum` inside it does the stacking, which every browser styles the
   same way. Keep it that way: do not put display on the summary again. */
.draftbox .qf > summary{list-style:none;cursor:pointer;display:block;
  background:var(--grey-bar);border-top:1px solid var(--hair);
  padding:10px 15px 12px;font-size:0.75rem;color:var(--muted);}
.draftbox .qf > summary .qsum{display:flex;flex-direction:column;
  align-items:flex-start;gap:8px;}
.draftbox .qf > summary::-webkit-details-marker{display:none;}
.draftbox .qf > summary::marker{content:"";}
.draftbox .qf .b{align-self:center;font-weight:700;color:var(--berry-deep);
  background:var(--card);border:1px solid var(--hair);border-radius:8px;
  padding:5px 12px;line-height:1.4;}
.draftbox .qf > summary:hover .b{background:var(--berry-soft);}
/* `.c` AND `.o` WERE A COLLISION, NOT A NAME. `.c` is this stylesheet's own
   global CARD class (background, 1px border, 12px radius, 16px padding), so
   `<span class="c">See full thread</span>` painted a white card INSIDE the
   button, and Emese saw two stacked boxes with the wider one showing round the
   edges of the narrower. David reported it four times — "This is still broken.
   You can see the other button underneath", "the button underneath is the right
   size, not the one on top" — and it was read three of those times as a flex or
   overlap fault, which it never was. A one-letter class in a shared stylesheet
   will always eventually mean something else. */
.draftbox .qf:not([open]) .opened,
.draftbox .qf[open] .shut{display:none;}
.draftbox .qf .full{padding:14px 15px;border-top:1px solid var(--hair);
  background:var(--card);white-space:normal;font-size:0.875rem;line-height:1.65;}
.draftbox .qf .full p{margin:0 0 0.55em;}
.draftbox .qf .full p:last-child{margin-bottom:0;}

/* ---------- RULING 64: OUR OWN CURATED THREAD ---------- */
/* What sits behind "See full thread" is no longer the material the sender's
   mail program quoted inside the latest message — it is the app's own rows for
   this person, in date order (see `_curated_thread`). David, 8 Sep 2026:
   "separate out each message with a little header saying the date and the
   time, then the message, and then another little header neatly designed,
   maybe in a different colour, maybe shaded, and then the next message, so
   they're easily readable as separate things", and on what the header carries,
   "in that message's little heading it might say 'via Counselling Directory',
   with the date etc., neatly formatted."

   So an entry is a shaded header and a body, and the separation is the header
   itself plus a hairline — NOT a wall of text with rules dropped into it.

   TWO SHADES, BECAUSE A CONVERSATION HAS TWO VOICES. Theirs sits on the same
   grey the bar above uses; hers on the same berry the reply box's own header
   carries, so the two sides of the exchange are told apart at a glance without
   a word being spent saying so. Bodies stay white in both cases: the shading
   is the header's job ("maybe shaded" was said of the header), and tinting the
   text as well would make her own words the odd ones out on a white screen.

   THE PANEL SCROLLS PAST HALF THE WINDOW. The longest conversation on file is
   84 messages, and a details panel that pushes the compose box off the bottom
   is the fault "only show their most recent message" existed to fix. Same
   device, same reason, as `.draftbox.theirs .body`'s own max-height. Nothing
   is hidden — every message is in the panel, the panel simply has an end. */
.draftbox .qf .thread{border-top:1px solid var(--hair);background:var(--card);
  max-height:56vh;overflow-y:auto;}
.draftbox .qf .thread .tmsg + .tmsg{border-top:1px solid var(--hair);}
.draftbox .qf .thread .th{display:flex;flex-wrap:wrap;align-items:baseline;
  gap:2px 12px;justify-content:space-between;padding:7px 15px;
  background:var(--grey-bar);border-bottom:1px solid var(--hair-2);}
/* Same type as `.draftbox .dh` above, one step quieter: this is a header on a
   header, and it must not compete with the box's own title. */
.draftbox .qf .thread .th .w{flex:1 1 auto;min-width:0;
  font-size:0.6875rem;text-transform:uppercase;letter-spacing:.07em;
  font-weight:700;color:var(--muted);}
.draftbox .qf .thread .th .d{flex:0 1 auto;text-align:right;
  font-size:0.75rem;color:var(--muted);font-variant-numeric:tabular-nums;
  white-space:nowrap;}
.draftbox .qf .thread .tmsg.mine .th{background:var(--berry-soft);}
.draftbox .qf .thread .tmsg.mine .th .w,
.draftbox .qf .thread .tmsg.mine .th .d{color:var(--berry-deep);}
.draftbox .qf .thread .tb{padding:12px 15px 14px;background:var(--card);
  white-space:normal;font-size:0.875rem;line-height:1.65;}
.draftbox .qf .thread .tb p{margin:0 0 0.55em;}
.draftbox .qf .thread .tb p:last-child{margin-bottom:0;}
.draftbox .qf .thread .tb .none{color:var(--faint);font-style:italic;}

/* PARAGRAPH GAPS AT HALF SIZE (David, 7 Sep 2026: "these paragraph breaks
   could be half the size", and the spec that settled it — "paragraph breaks
   look half the size in actual email on Google or on Apple"). The body used to
   render with `white-space: pre-line`, so an empty line in the email was an
   empty line here at the full line-height. A blank line has no element to give
   a margin to, so it is now real paragraphs (see `_as_paragraphs`) and this is
   the gap: about half a line, which is what Gmail and Apple Mail show. First
   and last paragraphs lose their outer margin so the box's own padding is not
   doubled. */
.draftbox .body p{margin:0 0 0.55em;}

/* THE BOX SHE TYPES IN. Two faults, one edit. The paragraph gap was a whole
   blank line because a textarea has no element to hang a margin on, and the
   type was a hardcoded 14px while everything around it scales with the
   reader's own root size — so on her big screen their message was LARGER than
   her own reply (David, 7 Sep 2026: "Why is there font bigger than [Emese]'s
   response font?"). Both now sit on the same 0.875rem and the same 1.65, so
   the letter reads at one size whichever half of it she is looking at. */
.draftbox .body.compose{padding:0;}
.draftbox .rawbox,
.draftbox .composer{width:100%;min-height:300px;font-family:inherit;
  font-size:0.875rem;line-height:1.7;color:var(--ink);padding:16px;
  box-sizing:border-box;}
/* 1.7, not 1.65 (David, 8 Sep 2026: "in the same font as the one above we've
   discussed all this"). The size was matched to their message on 7 September
   and the leading was left a fraction tighter, which is the kind of near-miss
   that reads as two different fonts without being one. `.draftbox .body` above
   is 1.7; this is the same number, not a similar one. */
/* `display:block` HERE OUTRANKED THE BROWSER'S OWN `[hidden]` RULE, so the
   textarea the script hides kept painting — and Emese saw her signature
   TWICE with a resize handle between the two, once in the hidden textarea and
   once in the composer that replaced it. David, 8 Sep 2026, with a
   screenshot: "it looks rather odd with two sign off panels", "and a huge
   space". A class selector beats `[hidden]`'s UA declaration, so hiding by
   attribute silently did nothing. Any element this app hides by attribute and
   also gives a `display` needs this pair. */
.draftbox .rawbox{border:none;resize:vertical;background:transparent;display:block;}
.draftbox .rawbox[hidden]{display:none;}
.draftbox .composer{outline:none;white-space:normal;overflow-wrap:break-word;}
.draftbox .composer:focus{background:#FFFDF8;}
.draftbox .composer p{margin:0 0 0.55em;}
.draftbox .composer p:last-child{margin-bottom:0;}
.draftbox .composer:empty::before{content:attr(data-placeholder);color:var(--faint);}

/* A LINK IN HER BOX LOOKS LIKE THE LINK THE CLIENT GETS (ruling 17). #1155cc
   is `email_render.LINK_COLOUR` verbatim — the same blue the anchor carries in
   the letter itself, so the box is not showing her an approximation of her own
   email. Not underlined on sight for the same reason the letter's is not.

   `user-select:all` and the cursor are the tell that it is one object rather
   than words: it is contenteditable=false, so a click selects the whole link
   and a delete removes the whole link. That is deliberate — she must be able
   to take a booking link out of a letter, and must not be able to edit half a
   payment URL by accident. */
.draftbox .composer a.sentlink{color:#1155cc;text-decoration:none;
  cursor:default;user-select:all;-webkit-user-select:all;}
.draftbox .composer a.sentlink:hover{text-decoration:underline;}
.draftbox .body p:last-child{margin-bottom:0;}
.draftbox .body p:first-child{margin-top:0;}

/* A form control inside a draft box is a block, not a word in a sentence.
   As an inline-block it sat on the body's own text baseline, which added 27px
   of line-box leading above it and 7px below — the "huge space above Hi Ade"
   David spotted on 7 Sep 2026. */
.draftbox .body textarea,
.draftbox .body input,
.draftbox .body select{display:block;}

/* A FIELD HAS TO LOOK LIKE A FIELD. David, 7 Sep 2026, sent the name box
   back: "Is this a button?" It was a borderless full-bleed input filling a
   white card, so its placeholder read as a label on a slab. It now has its
   own edge inside the box, and the box is only as tall as it holds. */
.draftbox .body .fld{display:block;width:100%;font-family:inherit;
  font-size:0.875rem;color:var(--ink);background:var(--card);
  border:1px solid var(--hair);border-radius:8px;padding:9px 11px;}
.draftbox .body .fld:focus{outline:2px solid var(--berry-soft);
  border-color:var(--berry);outline-offset:1px;}

/* "Open a case for them" when nobody signed the message: a button that opens
   the one field it needs, and nothing on screen until she presses it. */
.askname{display:inline-block;}
.askname > summary{list-style:none;cursor:pointer;}
.askname > summary::-webkit-details-marker{display:none;}
.askname[open] > summary{opacity:.55;}
.askname .askrow{display:flex;gap:8px;align-items:center;margin-top:9px;
  flex-wrap:wrap;}
.askname .askrow .fld{flex:1 1 240px;width:auto;display:block;
  font-family:inherit;font-size:0.875rem;color:var(--ink);
  background:var(--card);border:1px solid var(--hair);border-radius:8px;
  padding:9px 11px;}

.actionbar{display:flex;align-items:center;gap:9px;flex-wrap:wrap;row-gap:9px;}

/* ---------- Diary week grid ---------- */
.weekwrap{flex:1;display:flex;flex-direction:column;min-width:0;}
.weektop{padding:14px 20px 12px;border-bottom:1px solid var(--hair);display:flex;align-items:center;gap:14px;background:var(--bone);}
.weektop .h{font-size:1.1875rem;font-weight:700;letter-spacing:-.01em;}
.weektop .nav-w{display:flex;align-items:center;gap:4px;}
.weektop .nav-w a{border:1px solid var(--hair);background:#fff;border-radius:8px;width:30px;height:30px;
  font-size:0.9375rem;color:var(--muted);font-family:inherit;display:flex;align-items:center;justify-content:center;
  text-decoration:none;cursor:pointer;}
.weektop .nav-w a:hover{background:var(--hair-2);}
.weektop .today-btn{border:1px solid var(--hair);background:#fff;border-radius:8px;padding:6px 13px;font-size:0.8125rem;
  font-weight:600;color:var(--ink);font-family:inherit;text-decoration:none;cursor:pointer;}
.weektop .today-btn:hover{background:var(--hair-2);}

.week{flex:1;display:flex;background:var(--ground);overflow:auto;min-height:0;}
.week .axis{flex:0 0 46px;display:flex;flex-direction:column;color:var(--muted);font-size:0.6875rem;padding-top:30px;}
.week .axis .h{flex:1;text-align:right;padding-right:7px;}
/* 94px, not 110px, and the number is the whole point (30 Aug 2026, C4). Six
   columns at 110px need 660px; SEVEN at 94px need 658px. So Sunday costs no
   usable width at all: the week starts scrolling sideways at exactly the same
   place it did before, and a week you have to scroll is no longer a week. What
   it does cost is text inside each block, about 90px down to 74px. */
.daycol{flex:1;display:flex;flex-direction:column;border-left:1px solid #DFDBD3;background:#fff;min-width:94px;}
.daycol:nth-child(even){background:#FCFBF8;}
.daycol .dch{flex:0 0 30px;text-align:center;font-size:0.75rem;color:var(--muted);font-weight:600;
  border-bottom:1px solid var(--hair);display:flex;flex-direction:column;justify-content:center;line-height:1.15;}
.daycol .dch.on{color:var(--berry);}
.daycol .dch b{font-size:0.9375rem;display:block;}
.daycol .body{flex:1;position:relative;}
.ap{position:absolute;left:3px;right:3px;border-radius:7px;padding:4px 7px;font-size:0.6875rem;line-height:1.3;
  overflow:hidden;cursor:pointer;text-decoration:none;display:block;}
.ap .who{font-weight:700;font-size:0.71875rem;}
.ap .mt{font-size:0.625rem;opacity:.85;}
.ap.confirmed{background:var(--ok-soft);border-left:3px solid var(--ok);color:var(--ok);}
.ap.completed{background:#EEEDEA;border-left:3px solid var(--faint);color:var(--muted);}
/* C4/§6.4.2 (27 Aug 2026) — the four statuses the diary could not draw until
   today. SIBLINGS, never a reuse of .confirmed or .completed (§6.4.2's own
   instruction): a dead session and a live one must not share a fill.
   A hold is hatched rather than filled, so "not yet paid for" reads as
   provisional at a glance without a second vocabulary — David's point that the
   time remaining is what distinguishes the two clocks, so the block itself only
   has to say "unpaid". */
.ap.cancelled{background:#F4F1F1;border-left:3px solid var(--faint);color:var(--muted);
  text-decoration:line-through;text-decoration-thickness:1px;}
.ap.no_show{background:#F7F0EE;border-left:3px solid #C8A08F;color:#8A6A5C;}
.ap.pending{background:repeating-linear-gradient(135deg,var(--warn-soft),var(--warn-soft) 5px,#FFF8EC 5px,#FFF8EC 10px);
  border-left:3px dashed var(--warn);color:#8A6A2A;}
.ap.pending.hold-lapsed{border-left-style:solid;border-left-width:4px;}
.ap.held_for_review{background:#F1F0F6;border-left:3px dotted #8B86A8;color:#5F5A7A;}
/* A5a / C5 (28 Aug 2026) — the engineered break. Its own vocabulary, on the
   same rule as the four above: a break is not a session and must not borrow a
   session's fill. Quieter than every one of them, because it is the only block
   on this grid that is NOT a client — no border accent, no cursor, low
   contrast. It reads as the diary's own mark rather than as something with a
   person behind it, which is exactly what it is. */
.ap.brk{background:#F2F1EE;color:var(--faint);cursor:default;
  border:1px dashed var(--hair);border-left:3px solid var(--hair);}
.ap.brk .who{font-weight:600;}
/* §6.6D (1 Sep 2026) — the away day, greyed. A whole-day period colours the
   WHOLE column (no block needed, nothing to click); a partial day draws a
   block, quieter even than a break, since it is not something she booked
   and not something anyone can book over. */
.daycol.away{background:repeating-linear-gradient(135deg,#F2F1EE,#F2F1EE 7px,#EAE8E3 7px,#EAE8E3 14px);}
.daycol.away .dch{color:var(--faint);}
.daycol.away .away-note{position:absolute;top:34px;left:3px;right:3px;font-size:0.625rem;
  color:var(--faint);text-align:center;}
.ap.away{background:repeating-linear-gradient(135deg,#F2F1EE,#F2F1EE 7px,#EAE8E3 7px,#EAE8E3 14px);
  color:var(--faint);cursor:default;border:1px dashed var(--hair);z-index:0;}
.ap.away .who{font-weight:600;}
/* §6.6B — a released booking: the client has been invited elsewhere and this
   row is neither live nor simply dead, so it gets its own quiet mark rather
   than borrowing .cancelled's strikethrough (nothing here was cancelled). */
.ap.released{background:#F4F2EE;border-left:3px solid var(--faint);color:var(--muted);
  font-style:italic;}
/* Stacking: a live session (z2) draws over a dead one (z1) in the same slot,
   and the selected block still wins over both. Set as CLASSES rather than an
   inline style — an inline z-index would have beaten `.ap.sel`'s own, quietly
   putting a cancelled row on top of the one Emese had just clicked. */
.ap.z1{z-index:1;}
.ap.z2{z-index:2;}
.ap.sel{box-shadow:0 0 0 2px var(--berry);z-index:3;}

.rail{flex:0 0 296px;border-left:1px solid var(--hair);background:#fff;overflow:auto;padding:18px 18px 20px;min-width:0;}
.rail h4{margin:0 0 10px;font-size:0.6875rem;text-transform:uppercase;letter-spacing:.09em;color:var(--faint);font-weight:700;}
.rail .blk{margin-bottom:20px;}
.kv{display:flex;justify-content:space-between;font-size:0.8125rem;padding:8px 0;border-bottom:1px solid var(--hair-2);gap:10px;}
.kv:last-child{border-bottom:none;}
.kv .k{color:var(--muted);}
.kv .v{font-weight:600;text-align:right;}
.kv .v.ok{color:var(--ok);}
.kv .v.berry{color:var(--berry);}

/* ---------- Clients ---------- */
.searchbar{margin-top:11px;background:var(--card);border:1px solid var(--hair);border-radius:999px;
  padding:8px 13px;font-size:0.8125rem;color:var(--faint);display:flex;align-items:center;gap:7px;}
.searchbar input{border:none;background:transparent;font-size:0.8125rem;font-family:inherit;outline:none;flex:1;color:var(--ink);min-width:0;}
/* Night brief 2 item 3 (6 Sep 2026) — the emoji 🔍 becomes the stroked Lucide
   mark, at the bottom bar's own 1.8px weight so the two read as one set.
   currentColor, so it inherits .searchbar's --faint here and would follow any
   other context it is dropped into. */
.searchbar .ic-search{width:16px;height:16px;flex:0 0 auto;stroke:currentColor;fill:none;
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
.filters{margin-top:11px;display:flex;gap:6px;flex-wrap:wrap;}
.pill{font-size:0.75rem;padding:6px 13px;border-radius:999px;border:1px solid var(--hair);
  background:var(--card);color:var(--muted);display:inline-block;text-decoration:none;}
.pill.on{background:var(--berry);border-color:var(--berry);color:#fff;font-weight:600;}
.btn.warn{background:#fff;color:var(--warn);border:1px solid #EAD9B7;}
.btnrow{display:flex;gap:9px;flex-wrap:wrap;}
.btnrow .btn{flex:1;}

/* A banner with no fill of its own is not a banner: it is grey text on the page's
   own bone colour, which is how "Saved. 4 prices updated" read as the app doing
   nothing at all (27 Aug 2026, the prices walk). The base now carries the neutral
   look, so an uncategorised flash and the seven static class="banner" notices in
   todo/prices_confirm/stamp_rates are visible; .warn and .berry below override it
   for the two states that must never be confused at a glance. The border shorthand
   also has to exist here for todo.html's inline border-color to have anything to
   colour — it was setting a colour on a border that was never drawn. */
.banner{border-radius:10px;padding:11px 14px;font-size:0.9375rem;margin-bottom:14px;line-height:1.5;
        background:var(--card);border:1px solid var(--hair);}
.banner.warn{background:var(--warn-soft);color:#7A5410;border:1px solid #EAD9B7;}

.tl{position:relative;padding-left:20px;}
.tl::before{content:"";position:absolute;left:5px;top:6px;bottom:6px;width:2px;background:var(--hair);}
.tl .ev{position:relative;padding:0 0 18px 6px;}
.tl .ev::before{content:"";position:absolute;left:-18px;top:4px;width:9px;height:9px;border-radius:50%;
  background:var(--berry);border:2px solid var(--bone);}
.tl .ev.muted::before{background:var(--faint);}
.tl .ev .d{font-size:0.6875rem;color:var(--faint);font-weight:600;}
.tl .ev .t{font-size:0.875rem;font-weight:600;margin-top:1px;}
.tl .ev .s{font-size:0.8125rem;color:var(--muted);margin-top:1px;}
.tl .ev .quote{font-size:0.8125rem;color:var(--muted);background:#fff;border:1px solid var(--hair);
  border-radius:9px;padding:9px 11px;margin-top:7px;line-height:1.55;}

/* ---------- "What they've told you" (evening ruling 35) ---------------------
   Their WORDS, and the styling's whole job is to make it obvious they are
   quotes rather than the app's prose — she is reading them back four minutes
   before a session and must not mistake a summary for a sentence someone
   wrote. First build borrowed the client record's `.tl` timeline for this,
   which drew a vertical rail and a dot per entry off the left edge of the
   card: a timeline is about sequence, and this block is about voices. */
.said{margin-top:12px;}
.said:first-of-type{margin-top:4px;}
/* NOT scoped to `.said`: the block's own section labels ("Intake form ·
   completed 14 Jun") use it too, and scoping it left those inheriting the
   body's 14px, so a heading meant to be quieter than the quotes under it
   rendered larger than them. */
.from{font-size:0.6875rem;color:var(--faint);font-weight:600;letter-spacing:.02em;}
.said .quote{font-size:0.84375rem;color:var(--ink);background:var(--bone);border:1px solid var(--hair);
  border-left:3px solid var(--hair-2);border-radius:9px;padding:10px 12px;margin-top:5px;
  line-height:1.6;white-space:pre-line;}

/* ---------- Money ---------- */
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:16px;}
.stat{background:var(--card);border:1px solid var(--hair);border-radius:12px;padding:14px 16px;}
.stat .n{font-size:1.375rem;font-weight:800;letter-spacing:-.01em;}
.stat .n.berry{color:var(--berry);} .stat .n.ok{color:var(--ok);} .stat .n.warn{color:var(--warn);}
.stat .l{font-size:0.75rem;color:var(--muted);margin-top:2px;}
.banner.neutral{background:var(--card);border:1px solid var(--hair);color:var(--muted);}
.banner.berry{background:var(--berry-soft);color:var(--berry-deep);border:1px solid #EBD3DE;}
.kv .v.berry{color:var(--berry);}
.actionbar .spacer{flex:1;}

/* ---------- Forms ---------- */
.librow{display:flex;align-items:center;gap:11px;padding:11px 16px;border-bottom:1px solid var(--hair-2);
  text-decoration:none;color:inherit;}
.librow:hover{background:#fff;}
.librow.sel{background:#fff;box-shadow:inset 3px 0 0 var(--berry);}
.librow .body{flex:1;min-width:0;}
.librow .body .t{font-size:0.875rem;font-weight:600;}
.librow .body .s{font-size:0.75rem;color:var(--muted);margin-top:1px;}
.qrow.flat{padding-left:0;padding-right:0;cursor:default;}
/* Section heading inside a .listscroll (Money, Forms). This class was used by
   both templates and defined by neither, so it rendered as an unstyled div at
   zero offset while every .qrow beside it carried 16px of side padding — the
   heading sat hard against the left edge, out of line with the title above it
   and the rows it labelled. One missing rule, both screens. (24 Aug 2026
   dress rehearsal, raised twice as separate findings before the shared cause
   was found.) Padding matches .qrow's so headings and rows share one margin. */
.sectlabel{padding:14px 16px 6px;font-size:0.6875rem;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;color:var(--faint);}
.sectlabel:first-child{padding-top:6px;}

/* ---------- Homework library ---------- */
/* The library is the app's only full-width list, and its descriptions are real
   sentences rather than the one-line summaries .qrow .body .s was written for
   — so the shared nowrap+ellipsis clipped them mid-word at the right edge, and
   .qrow.flat's zero side padding ran them to the very edge of a very wide
   container. Here they wrap to a readable measure instead. (24 Aug 2026.) */
.libpane .qrow.flat{padding-left:26px;padding-right:26px;}
.libpane .qrow .body .s{white-space:normal;overflow:visible;text-overflow:clip;
  max-width:70ch;margin-top:3px;line-height:1.5;}

/* ---------- Schedule ---------- */
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start;}
.grid2 .c{margin-bottom:0;}
/* Day column sized to the longest day name ("Wednesday"), not a guessed pixel
   width — at 54px it overflowed into the hours column. */
.schedrow{display:grid;grid-template-columns:max-content 1fr auto;gap:12px;align-items:center;
  padding:11px 0;border-bottom:1px solid var(--hair-2);}
.schedrow:last-child{border-bottom:none;}
.schedrow .day{font-size:0.8125rem;font-weight:700;color:var(--ink);}
.schedrow .hours{font-size:0.8125rem;color:var(--ink);}
.schedrow .hours.off{color:var(--faint);font-style:italic;}
.schedrow .loctags{display:flex;gap:5px;}

/* ---------- Settings ---------- */
.span2{grid-column:1 / -1;}
.toggle{width:38px;height:22px;border-radius:999px;background:var(--berry);position:relative;flex:0 0 auto;}
.toggle::after{content:"";position:absolute;top:2px;right:2px;width:18px;height:18px;border-radius:50%;background:#fff;}
.toggle.off{background:var(--hair);}
.toggle.off::after{right:auto;left:2px;}

/* ---------- The Dashboard (night brief 1, 6 Sep 2026) ----------------------
   THIS BLOCK REPLACES the two-column `.dash` grid and the `.tlrow` / `.gap`
   session-timeline rules, and it is a rewrite rather than a deletion because
   what those rules drew is what these draw: `.dash` was the two-pane Today
   (to-do card beside sessions timeline), `.tlrow` was one session in that
   timeline and `.gap` was the "3½ hours free" note between two of them. All
   three came off the screen by ruling — the to-do card and the timeline on
   5 Sep, the timeline's imminence job going to the Up Next card — so the
   grid they shared is now the eight-tile grid, and the row is now a tile.
   Nothing that used them survives; `grep -rn "tlrow\|class=\"gap\|class=\"dash" templates/`
   returns nothing. */
.c{background:var(--card);border:1px solid var(--hair);border-radius:12px;padding:16px;margin-bottom:14px;}
.c h4{margin:0 0 10px;font-size:0.6875rem;text-transform:uppercase;letter-spacing:.08em;color:var(--faint);font-weight:700;}
.empty{font-size:0.8125rem;color:var(--faint);padding:8px 0;}

/* The date beside the title: "Dashboard · Mon 17 Oct". Its own class rather
   than an inline style, because both widths and the phone header read it. */
.panehead .paneday{font-weight:500;color:var(--muted);font-size:0.9375rem;}
/* Evening ruling 17 — the desk quick-adds sit TOP-RIGHT beside the tiles, on
   the title's own line, not on a row of their own beneath it. `.panehead` is
   a plain block used by every screen in the app, so the row behaviour is
   scoped to the head that actually carries controls rather than imposed on
   all of them. `align-items:baseline` so the buttons sit on the title's
   baseline, which is what the drawing shows. */
.panehead.withadds{display:flex;align-items:baseline;justify-content:space-between;gap:16px;}
.panehead.withadds .quickadds{margin-top:0;flex:0 0 auto;}

/* THE UP NEXT CARD. Absent from the markup when nothing is upcoming, so
   there is no empty state to style. */
.hero{background:var(--card);border:1px solid var(--hair);border-radius:var(--radius);
  box-shadow:0 1px 2px rgba(33,32,28,.05),0 2px 8px rgba(33,32,28,.04);
  padding:16px 20px;margin-bottom:16px;display:flex;align-items:center;gap:18px;max-width:1040px;}
.hero .hwhen{flex:0 0 auto;font-size:1.75rem;font-weight:800;color:var(--berry-deep);line-height:1;}
.hero .hwhen small{display:block;font-size:0.625rem;font-weight:700;color:var(--faint);
  letter-spacing:.06em;margin-top:4px;}
.hero .hbody{flex:1;min-width:0;}
.hero .ht{font-size:1rem;font-weight:700;}
.hero .hs{font-size:0.8125rem;color:var(--muted);margin-top:1px;}

/* THE EIGHT TILES. Four across on a desk, two on a phone (the phone rule is
   in the 768px block). EQUAL HEIGHTS is a fixed row height rather than a
   min-height, because David's ruling is that they match: a sub-line that
   wraps to two lines on one tile must not make that tile taller than its
   neighbour. Captions are therefore one line by contract, kept short in the
   template. Text centred; no chevron and no trend arrow anywhere. */
.tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;max-width:1040px;}
.tile{background:var(--card);border:1px solid var(--hair);border-radius:var(--radius);
  box-shadow:0 1px 2px rgba(33,32,28,.05),0 2px 8px rgba(33,32,28,.04);
  padding:16px;height:132px;display:flex;flex-direction:column;justify-content:space-between;
  align-items:center;text-align:center;text-decoration:none;color:inherit;overflow:hidden;}
a.tile:hover{border-color:var(--faint);}
.tile .k{font-size:0.65625rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--faint);}
.tile .n{font-size:2.5rem;font-weight:800;letter-spacing:-.02em;color:var(--berry-deep);line-height:1;}
.tile .n small{font-size:1.25rem;font-weight:700;color:var(--faint);}
.tile .s{font-size:0.75rem;color:var(--muted);line-height:1.3;}
.tile.warn{background:var(--warn-soft);border-color:var(--warn);}
.tile.warn .n{color:var(--warn);}
.tile.urgent{background:var(--berry-soft);border-color:var(--berry);}
.tile.urgent .n{color:var(--berry);}

/* The Money tile's week chart: seven bars, Monday first, the days that have
   not happened yet ghosted. Heights are a percentage of the week's own peak,
   set inline from the view — the only honest way to draw a bar whose scale is
   the data's, and the template floors every one at 4% so a zero day is a
   visible baseline rather than a missing bar. */
.bars{display:flex;align-items:flex-end;gap:4px;height:26px;}
.bars i{display:block;width:11px;background:var(--berry);border-radius:2px 2px 0 0;opacity:.85;}
.bars i.ghost{background:var(--hair);opacity:1;}

/* THE QUICK-ADDS. Desk: three buttons top-right beside the tiles (evening
   ruling 17). Phone: the same three in one row at the top of the scroll,
   above the Up Next card (evening ruling 13). Labels centred, ruled at
   midday "here and elsewhere". */
.quickadds{display:flex;gap:12px;margin-top:12px;}
.quickadd{flex:0 0 auto;min-width:124px;display:flex;align-items:center;justify-content:center;gap:9px;background:var(--card);
  border:1px solid var(--hair);border-radius:10px;padding:9px 13px;text-decoration:none;cursor:pointer;}
.quickadd:hover{background:var(--hair-2);}
.quickadd b{font-size:0.8125rem;font-weight:600;color:var(--ink);display:block;white-space:nowrap;}

.deeplink{display:flex;align-items:center;gap:11px;padding:11px 0;border-bottom:1px solid var(--hair-2);}
.deeplink:last-child{border-bottom:none;}
.deeplink .body{flex:1;min-width:0;}
.deeplink .body .t{font-size:0.875rem;font-weight:600;}
.deeplink .body .s{font-size:0.75rem;color:var(--muted);margin-top:1px;}
.deeplink .go{color:var(--faint);font-size:0.875rem;flex:0 0 auto;}

/* Sign-in */
.signin-body{display:flex;align-items:center;justify-content:center;height:100vh;}
.signin-card{background:var(--card);border:1px solid var(--hair);border-radius:16px;box-shadow:var(--shadow-lg);
  padding:32px;width:280px;display:flex;flex-direction:column;gap:14px;}
.signin-card label{font-size:0.75rem;font-weight:600;color:var(--muted);}
.signin-card input{font-size:1.375rem;letter-spacing:.3em;text-align:center;padding:10px;border:1px solid var(--hair);
  border-radius:10px;font-family:inherit;}
/* Four boxes for the PIN. The single field they replace showed four dots with
   no sense of progress; these give one glance at how far in she is. The posted
   value is still one four-character `pin` from a hidden field. */
.pinboxes{display:flex;gap:10px;justify-content:center;}
.pinbox{width:52px;height:58px;font-size:1.5rem;text-align:center;padding:0;
  border:1px solid var(--hair);border-radius:12px;background:var(--card);
  color:var(--ink);font-family:inherit;}
.pinbox:focus{outline:none;border-color:var(--berry);box-shadow:0 0 0 3px var(--berry-soft);}
.signin-error{color:#B23B3B;font-size:0.8125rem;}

/* ---------- Piece #21: responsive tiers ----------
   The three-tier split decided 30 Jul 2026 (plan.md § The mobile split).
   Tier 1 is absent by construction: the client-facing templates are
   standalone single-column shells that never extend base.html, so they carry
   their own viewport and are already phone-first.

   One breakpoint. Below 768px is a phone; above it a tablet in landscape or a
   desk, where the 244px sidebar plus a 360px list plus a 296px rail still fit.
   The tier a screen belongs to is set server-side (PHONE_TIER_2_ENDPOINTS in
   app.py) and arrives here as a class on <body>, so the CSS never has to guess
   from a URL. */

.narrownotice{display:none;}
/* The bottom bar is phone-only chrome; the desk keeps its sidebar. */
.tabbar{display:none;}

/* ---------- the two width utilities (night brief 2 item 7's wall sweep) ----
   The tier is decided SERVER-SIDE, by endpoint — so re-pointing a link away
   from a Tier-3 screen moves it on the desk as well, where that screen is
   the right destination and the phone's smaller one is a downgrade. These
   let a template emit BOTH anchors and let the width choose, which is the
   only way to send a phone somewhere else without taking the desk with it.
   Use them in pairs, and never for anything but a destination: content that
   only one width may see is a different question. */
.only-phone{display:none;}

@media (max-width:768px){

  /* --- the shell: the sidebar becomes a HEADER (both tiers) --- */
  html,body{height:auto;}
  .app{flex-direction:column;height:auto;min-height:100vh;}
  .side{flex:0 0 auto;width:100%;border-right:none;border-bottom:1px solid var(--hair);}

  /* Night brief 2 items 1 and 3 (6 Sep 2026). What .side is on a phone
     CHANGED here: it was a brand line plus a nav strip; it is now the header
     shell — sign-out at the left, the PRACTICE DESK wordmark centred at the
     very top, search at the right edge — and NAVIGATION MOVED TO THE BOTTOM
     BAR below.

     .side is NOT hidden and must not be: it is the only place the sign-out
     POST and its CSRF token live, and the only search entrance on the phone.
     Hiding it would take both off the phone silently.

     One row, three cells, in grid rather than DOM order — the markup order is
     brand, search, navstrip, foot, and reordering the markup would move the
     desk sidebar too. */
  .side{display:grid;grid-template-columns:auto 1fr auto;align-items:center;
    gap:10px;padding:9px 14px;}
  .side .brand{grid-area:1/2;padding:0;border-bottom:none;min-width:0;}
  /* No brandmark to hide any more — evening ruling 25 took the <img> out of
     this shell at both widths, so the phone header and the desk sidebar now
     carry the same single mark. */
  .side .brand .wordmark{margin-top:0;white-space:nowrap;}
  /* 11px here, not the sidebar's 13px: the phone header is one row shared
     with sign-out and search, and the D1 drawing's wordmark is the small
     one. */
  .side .brand .wordmark-chrome{font-size:0.6875rem;}
  /* The typing box is a DESK control. On the phone the glyph replaces it —
     one stroked mark matching the bottom bar, tapping through to the search
     screen where there is room to type. */
  .sidesearch{display:none;}
  .searchglyph{grid-area:1/3;justify-self:end;display:flex;align-items:center;
    justify-content:center;width:34px;height:34px;color:var(--muted);}
  .searchglyph .ic-search{width:19px;height:19px;stroke:currentColor;fill:none;
    stroke-width:1.8;stroke-linecap:round;}
  /* SUPERSEDED, and the rule is kept rather than deleted so the reason is
     readable: both nav groups used to flow into ONE sideways-scrolling strip
     here, on the argument that a 244px column would shoulder the content off
     the screen. The strip carried all eleven destinations, EIGHT of which are
     Tier 3 — a navigation whose majority walls. The bottom bar's three live
     destinations replace it (night brief 2 item 1); the strip's own rules
     stay below it because they are what the desk sidebar's phone fallback
     would need again if the bar were ever pulled. */
  .navstrip{display:none;}
  .navgrp{display:contents;}
  .navgrp .lbl{display:none;}
  .nav{white-space:nowrap;margin-bottom:0;padding:8px 12px;}
  .side .divider{display:none;}
  /* THE WHOLE FOOT GOES ON A PHONE (David, 6 Sep 2026 night: "that sign out
     button is new and unnecessary"). Signing out is desk housekeeping; on her
     own phone, between two clients, it is a control that can only cost her
     something. The form stays in the markup for the desk, where it belongs. */
  .side .foot{display:none;}

  /* --- the bottom bar --- */
  .tabbar{display:flex;justify-content:space-around;position:fixed;left:0;right:0;bottom:0;
    z-index:40;background:#fff;border-top:1px solid var(--hair);
    padding:6px 0 calc(6px + env(safe-area-inset-bottom,0px));}
  .tabbar .tab{display:flex;flex-direction:column;align-items:center;gap:2px;position:relative;
    padding:4px 8px;min-width:56px;font-size:0.6875rem;font-weight:500;color:#5a5650;
    text-decoration:none;}
  .tabbar .tab.on{color:var(--berry);}
  .tabbar .tab .ic svg{display:block;width:22px;height:22px;stroke:currentColor;fill:none;
    stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;}
  .tabbar .badge{position:absolute;top:0;right:4px;background:var(--warn-soft);color:var(--warn);
    font-size:0.5625rem;font-weight:700;border-radius:999px;padding:1px 5px;line-height:1.5;}
  /* Nothing hides under the bar — on either tier. The Tier-3 notice is a
     separate box outside .app, so it needs its own clearance. */
  .app{padding-bottom:62px;}
  body.tier-3 .narrownotice{padding-bottom:86px;}

  /* --- Tier 2: stack to a single column --- */
  .content{flex-direction:column;}
  /* MASTER AND DETAIL. Stacked, the list came first and pushed the detail off
     the bottom of the screen; with something open the list steps aside and
     the detail gets the phone to itself. `.backtolist` is the way back — a
     phone has no second pane to glance at, so the route out has to be a
     control rather than a turn of the head.

     OPT IN, NEVER OPT OUT. This was first written as
     `.detail:not(.pane-open)`, which reads sensibly and is keyed on the
     absence of a class only one template sets — so it hid the contents of
     the twenty-three screens that had never heard of panes, on every phone,
     while the desk stayed perfect. A rule that hides on an absence is silent
     exactly where it is wrong. `.phonepane` is the screen putting its hand
     up: I am a list and a detail, show one of us at a time. */
  .listpane.pane-behind{display:none;}
  .detail.phonepane:not(.pane-open) .panehead,
  .detail.phonepane:not(.pane-open) .detailscroll{display:none;}
  .backtolist{display:block;padding:12px 16px 4px;font-size:0.875rem;font-weight:600;
    color:var(--berry-deep);text-decoration:none;}
  /* The panes stop competing for a fixed viewport and let the page itself
     scroll, which is what a thumb expects. */
  .detail,.weekwrap{flex:0 0 auto;}
  .detailscroll{overflow:visible;padding:16px;}
  .panehead{padding:14px 16px 10px;}
  .grid2{grid-template-columns:1fr;}

  /* --- The Dashboard on a phone --- */
  .panehead .paneday{font-size:0.875rem;}
  /* TWO ACROSS, shorter, and still equal heights. The desk's 132px carries a
     40px figure; at half the width the figure is 28px and the row is 104px,
     which is the D1 drawing's own proportion. */
  .tiles{grid-template-columns:1fr 1fr;gap:8px;}
  .tile{height:104px;padding:10px;}
  .tile .n{font-size:1.75rem;}
  .tile .n small{font-size:1.1875rem;}
  .tile .k{font-size:0.625rem;}
  .tile .s{font-size:0.6875rem;}
  .bars{height:22px;gap:3px;}
  .bars i{width:9px;}
  .hero{padding:12px 14px;gap:12px;margin-bottom:10px;}
  .hero .hwhen{font-size:1.375rem;}
  .hero .ht{font-size:0.875rem;}
  .hero .hs{font-size:0.75rem;}

  /* THREE QUICK-ADDS AT 375px, and the arithmetic is why this is a grid.
     `.quickadd`'s min-width:124px times three plus two 12px gaps is 396px,
     which does not fit — so `flex-wrap:wrap` used to drop the third onto its
     own line, and the ruled design is ONE row. A three-column grid with
     min-width:0 children divides whatever width there is instead of
     demanding a width it cannot have; nowrap on the label is dropped with it,
     so a long label wraps inside its own button rather than pushing the row
     wide. */
  .quickadds{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;flex-wrap:nowrap;}
  .quickadd{min-width:0;padding:10px 4px;}
  .quickadd b{white-space:normal;font-size:0.75rem;}
  .phonequicks{margin-top:0;margin-bottom:12px;}
  /* Night brief 2 item 7 (6 Sep 2026) — phone-sizing Log call and Add
     booking, which join the phone tier tonight. Both are already single
     columns inside `.detail` at max-width:640px, so the work is the button
     rows: a `.btn` is inline-flex and does not wrap its own label, and
     `.btnrow`'s children carry `flex:1` with no min-width, so a pair like
     "Not free after all" / "Use the normal price" (and the date field beside
     "Show my free times") pushed past 375px instead of wrapping. Two rules,
     applied to every `.btnrow` in the app rather than to those two screens,
     because the fault is the row's and any screen joining the tier later
     would meet it again. */
  .btnrow > *{min-width:0;}
  .btnrow .btn{white-space:normal;text-align:center;}
  /* The rail sits under the screen it annotates rather than beside it. */
  .rail{flex:0 0 auto;width:100%;border-left:none;border-top:1px solid var(--hair);
    overflow:visible;padding:16px;}
  /* OVERRIDDEN 6 Sep 2026 (David, mid-walk 5 Sep; night brief 2 item 6). The
     decision recorded here used to read: "The diary keeps its seven-day grid
     and scrolls sideways inside its own box. The contract asks for a stack,
     not a rebuild into an agenda list, and seven columns at 110px cannot
     honestly fit a phone." That is still why this is a grid rather than an
     agenda list — but its column width was the part the ruling reversed:
     "four larger day cards with the fifth cropped at the edge as the swipe
     cue, never seven squeezed". At 375px the pinned 46px axis leaves 329px,
     so 80px columns show four whole days and a slice of the fifth, which is
     the cue; 94px showed three and a half, which reads as a clipped grid.
     The snap is what makes the swipe land on a day rather than mid-column. */
  .weektop{padding:12px 16px 10px;flex-wrap:wrap;row-gap:8px;}
  .week{min-height:420px;scroll-snap-type:x mandatory;scroll-padding-left:46px;}
  /* Four days fit; the rest arrive by swipe. The hour axis is pinned so it
     does not swipe away with them, which would leave the far end of the week
     as unlabelled boxes — and scroll-padding-left keeps a snapped column
     clear of it rather than under it. */
  .week .axis{position:sticky;left:0;z-index:2;background:var(--ground);}
  .daycol{min-width:80px;scroll-snap-align:start;}

  /* The pair that lets one template emit a desk destination and a phone one.
     `.only-phone` has to SET a display to become visible, and whatever it
     sets clobbers the element's own layout class — which is not academic:
     first build of this had the phone's Money and Clients tiles reverting to
     `block`, so their caption and figure ran along one line, and the phone's
     three quick-adds reverting out of the grid into a stack. Each element
     type that carries the utility therefore restates its own display at
     higher specificity. Add a line here when a new kind of element takes
     `.only-phone`; a bare `.only-phone` on a flex or grid box will look
     wrong on a phone and right on the desk, which is the hardest kind of
     fault to notice. */
  .only-desk{display:none;}
  .only-phone{display:revert;}
  .tile.only-phone{display:flex;}
  /* An .ap is an absolutely-positioned block in the diary grid; `revert`
     would make it inline and it would draw nowhere near its own slot. */
  .ap.only-phone{display:block;}
  .quickadds.only-phone{display:grid;}
  .qrow.only-phone{display:flex;}
  .btn.only-phone{display:inline-flex;}

  /* --- Tier 3: the notice REPLACES the layout --- */
  body.tier-3 .app{display:none;}
  body.tier-3 .narrownotice{display:flex;align-items:center;justify-content:center;
    min-height:100vh;padding:24px;background:var(--bone);}
  .narrownotice .nn{background:var(--card);border:1px solid var(--hair);
    border-radius:var(--radius);box-shadow:var(--shadow-lg);padding:24px;
    max-width:340px;text-align:center;}
  .narrownotice .nn-t{font-size:1.0625rem;font-weight:700;letter-spacing:-.01em;}
  .narrownotice .nn-s{font-size:0.875rem;color:var(--muted);margin-top:8px;line-height:1.55;}
  .narrownotice .btn{margin-top:18px;}
}

/* The laptop half of the same fix (David, 7 Sep 2026, on Emese's computer:
   "increase the size of the app... all the text looks tiny, full stop").
   The phone breakpoint above changes LAYOUT, never the base size, because
   375x812 was already reported legible and must not move. This breakpoint
   is the mirror of it — anything wider than a phone gets a larger root, and
   because every font-size in this file is now written in rem, that one
   number is the whole fix: no other line needs to change again for "make it
   bigger", and no line here can regress back to a deaf px without breaking
   the test that reads this file. 112.5% (18px against the 16px default) is
   a first, conservative step — several elements sit in fixed-width boxes
   (e.g. .pinbox, .side, .tile) that a much larger base would overflow — and
   is the one number to move if a larger step is wanted later. */
@media (min-width:769px){
  html{font-size:112.5%;}
}
