/* TheraGrid.
   Light and dark both defined on tokens; the page paints its own background
   so it never borrows the host's. */

:root {
  /* TELLING THE BROWSER, not just repainting round it. Every colour below is
     a variable this stylesheet controls; color-scheme is the half it does not
     own - the form controls, the select's own dropdown list, the date
     picker's panel, the scrollbars. Chrome draws all of those from its LIGHT
     defaults unless a page declares otherwise, and "otherwise" is this
     property and nothing else: a dark --surface does not imply it.
     Left undeclared, a bare <select> on a dark page came out rgb(239,239,239)
     with black text - measured on a handset, where the Unplaced page's two
     filters were white boxes on a near-black panel. It had to be a real
     device: emulating prefers-color-scheme in a desktop browser also flips
     the UA's own defaults, so the fault hides exactly where it would be
     looked for.
     It is declared three times, in step with the three places the tokens are,
     so the two can never disagree. */
  color-scheme:light;
  --ground:#F6F7F9; --surface:#FFFFFF; --raised:#FAFBFC; --sunken:#F0F2F5;
  --ink:#141A21; --muted:#5A6672; --faint:#8C98A4;
  --line:#E2E6EB; --line-soft:#EEF1F4;
  --accent:#1F6F5C; --accent-ink:#FFFFFF; --accent-bg:#E8F3EF;
  --accent-hover:#195A4A;
  --ok:#1B7F5A; --ok-bg:#E7F4EE;
  --bad:#C0392B; --bad-bg:#FBEBE9;
  --wait:#B7791F; --wait-bg:#FBF3E4;
  --info:#1D5FD6;   /* blue: a change in the history, beside --ok added and --bad removed */

  /* WhatsApp's own colours, for the one panel that deliberately looks
     like WhatsApp. Kept apart from the app's palette because they are not
     ours to theme: --accent can be changed and this must not follow it, or
     the panel stops being recognisable as the thing it is copying. */
  --wa:#008069; --wa-soft:#E7F6EF; --wa-line:#BFE3D4;
  --wa-paper:#EFEAE2; --wa-mine:#D9FDD3; --wa-tick:#53BDEB;
  /* And the rest of WhatsApp Web's own: the bar above and below the thread,
     their bubble, the box a reply is typed in, the ink and the small print
     inside a bubble, the date pill, and the faint doodle over the paper. */
  --wa-bar:#F0F2F5; --wa-theirs:#FFFFFF; --wa-field:#FFFFFF;
  --wa-ink:#111B21; --wa-meta:#667781; --wa-day:#FFFFFF; --wa-day-ink:#54656F;
  --wa-wall:url("data:image/svg+xml;utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='200'%20height='200'%20viewBox='0%200%20200%20200'%20fill='none'%20stroke='%234A3F35'%20stroke-opacity='.1'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%20%3Ccircle%20cx='26'%20cy='28'%20r='9'/%3E%20%3Cpath%20d='M70%2016l3.5%207.5%208%201-6%205.5%201.5%208-7-4-7%204%201.5-8-6-5.5%208-1z'/%3E%20%3Cpath%20d='M126%2032c-5-9-18-4-13%207%202%205%2013%2013%2013%2013s11-8%2013-13c5-11-8-16-13-7z'/%3E%20%3Cpath%20d='M170%2014v22a5.5%205.5%200%201%201-7-5.3V19l13-3'/%3E%20%3Cpath%20d='M24%2098c11-15%2028-17%2033-15-2%207-7%2022-29%2026-2-3-4-7-4-11zM27%20109l22-15'/%3E%20%3Cpath%20d='M88%20104h22M99%2093v22'/%3E%20%3Cpath%20d='M144%2092h24v15a9%209%200%200%201-9%209h-6a9%209%200%200%201-9-9zM168%2098h4a4.5%204.5%200%200%201%200%209h-4'/%3E%20%3Cpath%20d='M22%20168l9%209M31%20168l-9%209'/%3E%20%3Cpath%20d='M78%20164a13%2013%200%201%200%2013%2013'/%3E%20%3Cpath%20d='M124%20170c7-9%2016-9%2023%200-7%209-16%209-23%200z'/%3E%20%3Ccircle%20cx='135'%20cy='176'%20r='2.5'/%3E%20%3Cpath%20d='M172%20160l5%2010%2010%201.5-7.5%207%202%2010-9.5-5-9.5%205%202-10-7.5-7%2010-1.5z'/%3E%20%3Ccircle%20cx='100'%20cy='60'%20r='2'/%3E%3Ccircle%20cx='160'%20cy='130'%20r='2'/%3E%3Ccircle%20cx='56'%20cy='138'%20r='2'/%3E%20%3Ccircle%20cx='12'%20cy='130'%20r='2'/%3E%3Ccircle%20cx='188'%20cy='60'%20r='2'/%3E%20%3C/svg%3E");

  /* One radius scale, used deliberately: controls are the small one, cards and
     panels the middle, dialogs the large.
  
     THE SCALE WAS ADDED AND THE OLD VALUES WERE NOT REMOVED, which is worth
     writing down because the documentation claimed otherwise for months. A
     usability audit counted eleven distinct radii; the real number was
     eighteen, sitting on top of a three-token scale used thirty-five times.
     95 declarations have now been mapped onto the tokens. If a new hard-coded
     px value appears here, this is what it undoes. */
  /* The edge of a pencilled-in (not confirmed) chip. The clinic's own hex,
     asked for by name with a picture - "#FFFF33 ... as in attached pic" -
     and the same in both themes, because it was chosen to be seen on the
     coloured chips and not to match a palette. */
  --tent:#FFFF33;
  /* And the two cancellations, from the same desk the same way - two chips
     pictured with a solid ring and a solid badge each: "ED880C" for a
     cancellation, "F51902" for an immediate one. The same in both themes
     and both chip modes, for the same reason as --tent. */
  --canc:#ED880C; --imm:#F51902;
/* The edge on a "not booked" pill, asked for from the desk with a mockup:
   "boundaries should be orange color".

   NOT --canc, which is three pixels away in hue and means CANCELLED on this
   same grid - the clinic asked for that ring by name, with a picture, and a
   second orange ring on a different kind of thing would spend it. NOT --wait
   either: that is #D6A15A in the dark theme, a tan, and would not read as
   the orange that was drawn. One value for both themes, like the three above
   it, because what it marks is the same in both. */
  --miss:#F59E0B;

  --radius-xs:4px; --radius-sm:9px; --radius:12px; --radius-lg:16px;
  /* A pill: any number larger than half the tallest thing wearing one. There
     were two of these (99px and 999px) doing the identical job, plus a 20px
     that was a pill on a short element and read as a third size. */
  --radius-pill:999px;

  /* Elevation instead of outline. A card marked out by a 1px line looks drawn;
     one lifted off the page looks made. The lines stay - they carry structure
     inside a card - but the card's own edge is now mostly shadow. */
  --shadow-xs:0 1px 2px rgba(16,24,32,.05);
  --shadow-sm:0 1px 2px rgba(16,24,32,.06), 0 1px 3px rgba(16,24,32,.04);
  --shadow-md:0 2px 4px rgba(16,24,32,.05), 0 8px 20px rgba(16,24,32,.07);

  /* Keyboard focus. There was none: the browser's default outline is removed
     by `appearance:none` on every control here, and nothing replaced it, so
     tabbing through the app showed nothing at all. */
  --ring:0 0 0 3px rgba(31,111,92,.25);
  --ease:cubic-bezier(.4, 0, .2, 1);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="lavender"]):not([data-theme="blush"]) {
    color-scheme:dark;
    --ground:#0E1216; --surface:#161C22; --raised:#1B222A; --sunken:#11161B;
    --ink:#E8ECF1; --muted:#9AA7B4; --faint:#6B7885;
    --line:#242D36; --line-soft:#1D252D;
    --accent:#3FA588; --accent-ink:#06120E; --accent-bg:#14261F;
    --accent-hover:#57BCA0;
    --ok:#4BB98D; --ok-bg:#122A21;
    --bad:#E8705F; --bad-bg:#2B1613;
    --wait:#D6A15A; --wait-bg:#2A2113;
    --info:#6AA6F8;
    --wa:#00A884; --wa-soft:#10352C; --wa-line:#1D4A3E;
    --wa-paper:#0B141A; --wa-mine:#005C4B; --wa-tick:#53BDEB;
    --wa-bar:#202C33; --wa-theirs:#202C33; --wa-field:#2A3942;
    --wa-ink:#E9EDEF; --wa-meta:#8696A0; --wa-day:#182229; --wa-day-ink:#8696A0;
    --wa-wall:url("data:image/svg+xml;utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='200'%20height='200'%20viewBox='0%200%20200%20200'%20fill='none'%20stroke='%23FFFFFF'%20stroke-opacity='.045'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%20%3Ccircle%20cx='26'%20cy='28'%20r='9'/%3E%20%3Cpath%20d='M70%2016l3.5%207.5%208%201-6%205.5%201.5%208-7-4-7%204%201.5-8-6-5.5%208-1z'/%3E%20%3Cpath%20d='M126%2032c-5-9-18-4-13%207%202%205%2013%2013%2013%2013s11-8%2013-13c5-11-8-16-13-7z'/%3E%20%3Cpath%20d='M170%2014v22a5.5%205.5%200%201%201-7-5.3V19l13-3'/%3E%20%3Cpath%20d='M24%2098c11-15%2028-17%2033-15-2%207-7%2022-29%2026-2-3-4-7-4-11zM27%20109l22-15'/%3E%20%3Cpath%20d='M88%20104h22M99%2093v22'/%3E%20%3Cpath%20d='M144%2092h24v15a9%209%200%200%201-9%209h-6a9%209%200%200%201-9-9zM168%2098h4a4.5%204.5%200%200%201%200%209h-4'/%3E%20%3Cpath%20d='M22%20168l9%209M31%20168l-9%209'/%3E%20%3Cpath%20d='M78%20164a13%2013%200%201%200%2013%2013'/%3E%20%3Cpath%20d='M124%20170c7-9%2016-9%2023%200-7%209-16%209-23%200z'/%3E%20%3Ccircle%20cx='135'%20cy='176'%20r='2.5'/%3E%20%3Cpath%20d='M172%20160l5%2010%2010%201.5-7.5%207%202%2010-9.5-5-9.5%205%202-10-7.5-7%2010-1.5z'/%3E%20%3Ccircle%20cx='100'%20cy='60'%20r='2'/%3E%3Ccircle%20cx='160'%20cy='130'%20r='2'/%3E%3Ccircle%20cx='56'%20cy='138'%20r='2'/%3E%20%3Ccircle%20cx='12'%20cy='130'%20r='2'/%3E%3Ccircle%20cx='188'%20cy='60'%20r='2'/%3E%20%3C/svg%3E");
    /* Darker and deeper: on a dark ground a shadow tinted like the light one
       turns into a grey halo rather than reading as depth. */
    --shadow-xs:0 1px 2px rgba(0,0,0,.45);
    --shadow-sm:0 1px 2px rgba(0,0,0,.5), 0 1px 3px rgba(0,0,0,.35);
    --shadow-md:0 2px 4px rgba(0,0,0,.45), 0 8px 22px rgba(0,0,0,.5);
    --ring:0 0 0 3px rgba(63,165,136,.3);
  }
}

:root[data-theme="dark"] {
  color-scheme:dark;
  --ground:#0E1216; --surface:#161C22; --raised:#1B222A; --sunken:#11161B;
  --ink:#E8ECF1; --muted:#9AA7B4; --faint:#6B7885;
  --line:#242D36; --line-soft:#1D252D;
  --accent:#3FA588; --accent-ink:#06120E; --accent-bg:#14261F;
  --accent-hover:#57BCA0;
  --ok:#4BB98D; --ok-bg:#122A21;
  --bad:#E8705F; --bad-bg:#2B1613;
  --wait:#D6A15A; --wait-bg:#2A2113;
  --info:#6AA6F8;
  --wa:#00A884; --wa-soft:#10352C; --wa-line:#1D4A3E;
  --wa-paper:#0B141A; --wa-mine:#005C4B; --wa-tick:#53BDEB;
  --wa-bar:#202C33; --wa-theirs:#202C33; --wa-field:#2A3942;
  --wa-ink:#E9EDEF; --wa-meta:#8696A0; --wa-day:#182229; --wa-day-ink:#8696A0;
  --wa-wall:url("data:image/svg+xml;utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='200'%20height='200'%20viewBox='0%200%20200%20200'%20fill='none'%20stroke='%23FFFFFF'%20stroke-opacity='.045'%20stroke-width='1.5'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%20%3Ccircle%20cx='26'%20cy='28'%20r='9'/%3E%20%3Cpath%20d='M70%2016l3.5%207.5%208%201-6%205.5%201.5%208-7-4-7%204%201.5-8-6-5.5%208-1z'/%3E%20%3Cpath%20d='M126%2032c-5-9-18-4-13%207%202%205%2013%2013%2013%2013s11-8%2013-13c5-11-8-16-13-7z'/%3E%20%3Cpath%20d='M170%2014v22a5.5%205.5%200%201%201-7-5.3V19l13-3'/%3E%20%3Cpath%20d='M24%2098c11-15%2028-17%2033-15-2%207-7%2022-29%2026-2-3-4-7-4-11zM27%20109l22-15'/%3E%20%3Cpath%20d='M88%20104h22M99%2093v22'/%3E%20%3Cpath%20d='M144%2092h24v15a9%209%200%200%201-9%209h-6a9%209%200%200%201-9-9zM168%2098h4a4.5%204.5%200%200%201%200%209h-4'/%3E%20%3Cpath%20d='M22%20168l9%209M31%20168l-9%209'/%3E%20%3Cpath%20d='M78%20164a13%2013%200%201%200%2013%2013'/%3E%20%3Cpath%20d='M124%20170c7-9%2016-9%2023%200-7%209-16%209-23%200z'/%3E%20%3Ccircle%20cx='135'%20cy='176'%20r='2.5'/%3E%20%3Cpath%20d='M172%20160l5%2010%2010%201.5-7.5%207%202%2010-9.5-5-9.5%205%202-10-7.5-7%2010-1.5z'/%3E%20%3Ccircle%20cx='100'%20cy='60'%20r='2'/%3E%3Ccircle%20cx='160'%20cy='130'%20r='2'/%3E%3Ccircle%20cx='56'%20cy='138'%20r='2'/%3E%20%3Ccircle%20cx='12'%20cy='130'%20r='2'/%3E%3Ccircle%20cx='188'%20cy='60'%20r='2'/%3E%20%3C/svg%3E");
  --shadow-xs:0 1px 2px rgba(0,0,0,.45);
  --shadow-sm:0 1px 2px rgba(0,0,0,.5), 0 1px 3px rgba(0,0,0,.35);
  --shadow-md:0 2px 4px rgba(0,0,0,.45), 0 8px 22px rgba(0,0,0,.5);
  --ring:0 0 0 3px rgba(63,165,136,.3);
}

* { box-sizing:border-box; }

body {
  margin:0; background:var(--ground); color:var(--ink);
  font:14px/1.5 "IBM Plex Sans", system-ui, -apple-system, Segoe UI, sans-serif;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  /* The clinic's screens are full of dates, session numbers and rupee amounts
     stacked in columns. Proportional digits make those columns ragged - a 1 is
     half the width of a 0 - so every figure in the app lines up by default and
     a column of numbers can be read down rather than across. */
  font-variant-numeric:tabular-nums;
  /* Headings and figures are set tight; 14px body text is not. */
  text-rendering:optimizeLegibility;
}

[hidden] { display:none !important; }

/* A DROPDOWN'S OPEN LIST IN THE PAGE'S OWN COLOURS (23 October 2026),
   reported with the Birthday tab's "Sends as" open on the dark theme: a white
   list with the options in near-white, unreadable. That select draws its box
   on a wrapper (.iconfield) and is itself see-through, so Chrome opened its
   list on its own white while the options took the select's light dark-theme
   ink - color-scheme above does not reach a list whose select has no colour
   of its own. Every option says its colours here, whatever its select does,
   so no select can leave its list to the browser again. Light themes are as
   they were: their --surface is white and their --ink dark. */
select option, select optgroup { background-color:var(--surface); color:var(--ink); }
select option:disabled { color:var(--faint); }

/* Keyboard focus, which the app did not have. `appearance:none` on a control
   removes the browser's own focus ring along with its chrome, and nothing was
   put back, so tabbing through a form showed nothing whatsoever - unusable
   without a mouse, and the sort of omission that reads as unfinished.
   :focus-visible, not :focus, so a mouse click does not leave a ring behind. */
:where(button, a, input, select, textarea, summary, [tabindex]):focus-visible {
  outline:2px solid var(--accent);
  outline-offset:2px;
}

/* Interactive things settle rather than snap. 140ms is under the threshold
   where a transition starts to feel like waiting, and only the properties that
   actually change are listed - transitioning `all` catches layout as well and
   makes a resize crawl. */
:where(.btn, .navlink, .pill-btn, .linkbtn, .foldbtn, .tile, .dbig, .sess,
       input, select, textarea, .panel, .chipadd, .addbtn) {
  transition:background-color .14s var(--ease), border-color .14s var(--ease),
             color .14s var(--ease), box-shadow .14s var(--ease),
             transform .14s var(--ease), opacity .14s var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; scroll-behavior:auto !important;
  }
}

/* The default Windows scrollbar is 17px of grey chrome, and this app is mostly
   scrolling panes - the calendar, every table, the More menu. A thin one on
   the page's own colours stops them framing every panel. */
* { scrollbar-width:thin; scrollbar-color:var(--line) transparent; }
::-webkit-scrollbar { width:11px; height:11px; }
::-webkit-scrollbar-track { background:transparent; }
::-webkit-scrollbar-thumb {
  background:var(--line); border-radius:var(--radius-pill);
  border:3px solid transparent; background-clip:content-box;
}
::-webkit-scrollbar-thumb:hover { background:var(--faint); background-clip:content-box; }
::-webkit-scrollbar-corner { background:transparent; }

::selection { background:var(--accent-bg); color:var(--ink); }

/* Checkboxes and radios in the app's own colour rather than the operating
   system's. One property, the browser's own control, no replacement widget:
   a hand-built checkbox is a keyboard trap waiting to happen and this is a
   clinic tool, not a showcase. */
input[type="checkbox"], input[type="radio"] { accent-color:var(--accent); }

.spinner {
  display:inline-block; width:13px; height:13px; vertical-align:-2px;
  border:2px solid var(--line); border-top-color:var(--accent);
  border-radius:50%; animation:spin .7s linear infinite;
}
@keyframes spin { to { transform:rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation:none; } }

.boot { padding:56px 22px; color:var(--muted); display:flex; gap:9px; align-items:center; justify-content:center; }

/* THE PAGE'S SHAPE WHILE IT LOADS (loadingShape in ui.js): still a .boot, so
   every check that waits for a page to finish still sees it loading, but the
   day headings, time rows and pale bars where chips and rows will be, in the
   theme's own line colour - no colour of its own to clash in a dark theme. */
.boot.skel { display:block; padding:10px 0 4px; }
.skel-say {
  position:absolute; width:1px; height:1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap;
}
.skel i { display:block; height:10px; border-radius:var(--radius-pill); background:var(--line);
  animation:skel 1.4s ease-in-out infinite; }
@keyframes skel { 50% { opacity:.45; } }
@media (prefers-reduced-motion: reduce) { .skel i { animation:none; } }
.skel-grid {
  display:grid; grid-template-columns:56px repeat(var(--skelcols, 6), minmax(110px, 1fr));
  border-top:1px solid var(--line-soft); border-left:1px solid var(--line-soft);
}
.skel-grid > span { border-right:1px solid var(--line-soft); border-bottom:1px solid var(--line-soft);
  padding:8px; min-height:64px; display:flex; flex-direction:column; gap:7px; }
.skel-grid > .skel-head { min-height:0; padding:10px 8px; background:var(--sunken); }
.skel-head i { width:60%; height:12px; }
.skel-time i { width:70%; }
.skel .skel-chip { height:22px; border-radius:6px; }
.skel-list { display:flex; flex-direction:column; }
.skel-line { display:grid; grid-template-columns:2fr 1fr 1fr 1.4fr; gap:16px;
  padding:13px 12px; border-bottom:1px solid var(--line-soft); }
.skel-headline { background:var(--sunken); }
.skel-headline i { width:55%; height:9px; }

/* ----------------------------------------------------------- sign in ---- */

.signin {
  min-height:100vh; display:flex; align-items:center; justify-content:center;
  padding:24px;
  /* A single wash of the accent behind the card, so the page has a light
     source rather than being an even field of grey with a box on it. Two
     stops, no pattern: it should be felt and not looked at. */
  background:
    radial-gradient(1100px 620px at 50% -12%, var(--accent-bg), transparent 70%),
    var(--ground);
}

.signin .card {
  width:100%; max-width:372px; background:var(--surface);
  border:1px solid var(--line); border-radius:var(--radius-lg); padding:30px 28px;
  display:flex; flex-direction:column; box-shadow:var(--shadow-md);
}
/* The mark and the name are one object: the logo sat centred above a
   left-aligned heading, which put two different alignments in the first two
   elements anybody sees. */
.signin .mark-row { display:flex; align-items:center; gap:11px; }
.signin h1 { margin:0; font-size:22px; font-weight:650; letter-spacing:-.025em; }
.signin h1 span { color:var(--accent); }
.signin .sub { margin:9px 0 24px; color:var(--muted); font-size:13px; }
.signin label {
  font-size:11px; font-weight:600; color:var(--muted); margin-bottom:6px;
  letter-spacing:.03em;
}
.signin input {
  border:1px solid var(--line); background:var(--ground); color:var(--ink);
  border-radius:var(--radius-sm); padding:11px 12px; font-size:16px;
  margin-bottom:16px;
}
.signin input:hover { border-color:var(--faint); }
/* The ring rather than an inset outline: an outline drawn one pixel inside the
   border is a second rectangle on top of the first and reads as an error. */
.signin input:focus {
  outline:none; border-color:var(--accent); box-shadow:var(--ring);
  background:var(--surface);
}
.signin .btn { margin-top:4px; min-height:42px; font-size:14px; }
.err {
  margin:14px 0 0; padding:10px 12px; font-size:13px;
  background:var(--bad-bg); color:var(--bad); border-radius:var(--radius-sm);
}

/* The same shape in the affirmative. `.ok` on its own did not exist - only
   `.badge.ok` and two others - so a paragraph given it inherited body text
   and looked like something that had failed to be styled. */
.said {
  margin:14px 0 0; padding:10px 12px; font-size:13px; line-height:1.45;
  background:var(--ok-bg); color:var(--ok); border-radius:var(--radius-sm);
}

/* --------------------------------------------------------------- nav ---- */

/* The insets are the other half of viewport-fit=cover in index.html - see the
   comment on that meta tag. On anything without a notch or a gesture bar every
   one of these is 0, which is why they are unconditional rather than hidden
   behind a media query: there is nothing to detect.

   padding rather than margin, because .appnav paints var(--surface) and the
   strip behind the clock has to be painted by SOMETHING. With a margin the
   page's ground colour shows through there, which reads as a gap above the
   bar rather than as the bar reaching the top of the screen.

   Left and right matter in landscape, which is how a tablet is usually held:
   the notch moves to one side and would otherwise cut the centre's name or
   the account button in half. */
.appnav {
  position:sticky; top:0; z-index:30; background:var(--surface);
  border-bottom:1px solid var(--line);
  padding-top:env(safe-area-inset-top);
  padding-left:env(safe-area-inset-left);
  padding-right:env(safe-area-inset-right);
}
/* FULL WIDTH ON A DESKTOP, and this must change with .wrap below or the two
   stop lining up - the brand would start 290px in while the page it sits over
   starts at 20px, which reads as a broken header rather than a wide one. */
.appnav-inner {
  margin:0 auto; padding:0 16px; min-height:52px;
  display:flex; align-items:stretch; gap:10px;
}
.brand { margin-right:8px; display:flex; align-items:center; gap:0; font-size:15px; font-weight:600; color:var(--ink); text-decoration:none; white-space:nowrap; letter-spacing:-.01em; flex:none; }
.brand img { margin-right:9px; }
.brand span { color:var(--accent); }
/* Takes the space left over and no more, and hides nothing off the edge: what
   does not fit is moved into the More menu by fitNav, which needs this box to
   report the room actually available rather than the width of its contents.

   The basis is 0, not auto, and that is the whole trick. On `auto` the box asks
   for its content width, so it competes with the name beside it in proportion
   to how many links are showing - and then moving a link into the menu made the
   bar NARROWER, which changed the answer fitNav had just worked out from it.
   Measured it: 764px wide while overflowing, 707px once three links had moved,
   with the kept links needing 728. That is how More ended up 21px past the
   right edge, under `overflow:hidden`, invisible and unclickable.

   With a basis of 0 the bar's width falls out of the space left by the brand,
   the name and the buttons, and does not depend on its own contents at all. */
.navlinks {
  display:flex; align-items:stretch; gap:2px;
  flex:1 1 0; min-width:0; overflow:hidden;
}
.navlink {
  display:flex; align-items:center; padding:0 9px; font-size:13px; font-weight:500;
  color:var(--muted); text-decoration:none; border-bottom:2px solid transparent;
  white-space:nowrap; cursor:pointer; background:none; border-top:0; border-left:0; border-right:0;
  /* Never shrink. .navlinks below has flex:1 1 0 so the bar reports the room
     actually available rather than the width of its contents; a link that
     shrank to fit would defeat that by making everything fit, and fitNav would
     never move anything into the More menu. This lived in a second .navlink
     rule forty lines down, which is precisely the shape of the bug the CSS
     collision check exists to catch. */
  flex:none;
}
/* The whole tab lights, not just its word: a 13px label changing from grey to
   near-black is a change most people do not notice at all, and a nav bar that
   does not respond to the pointer feels like a picture of a nav bar. */
.navlink:hover { color:var(--ink); background:var(--sunken); }
.navlink.current { color:var(--ink); border-bottom-color:var(--accent); font-weight:600; }
.navlink.current .navicon { color:var(--accent); }

/* display:flex so the button inside stretches to the full 52px of the bar and
   centres its label like every other link. As a plain block it was 18px tall
   and sat against the top edge - the "cut off at the top" in the screenshot. */
.navmore { flex:none; display:flex; }
.appnav-inner .btn.ghost { white-space:nowrap; flex:none; }

/* ------------------------------------------------------------ shared ---- */

/* The 60px at the foot is what keeps the last row off the bottom edge, and on
   a device with a gesture bar it has to clear that too. It happened to be
   exactly enough on the tablet this was measured on - a 60px bar against 60px
   of padding, nothing to spare - which is luck rather than a margin. Adding
   the inset makes it hold on any bar; on a desktop the inset is 0 and this is
   unchanged. Phones override this in pages.css with a larger figure, for the
   page bar rather than the gesture bar. */
/* NO WIDTH CAP. It was 1340px, which on the 1920px laptop this runs on left
   290px unused down each side - and the pages that wanted it most are the
   ones this app is for. Measured on that screen with the cap in place: the
   Calendar's grid was scrolling sideways by 328px and Packages by 662px, both
   of them for room that was sitting empty on either side of the page.

   A cap like that is worth having for a page of prose, where a line 150
   characters long is genuinely harder to read than one half that. It is worth
   nothing for a table, which is read a row at a time and only ever suffers
   from being narrower than its columns. This app is tables and grids; the
   prose is a sentence under a heading, and that is capped where it is defined
   rather than by starving every grid behind it. */
.wrap {
  margin:0 auto;
  padding:22px 20px calc(60px + env(safe-area-inset-bottom));
}

/* One button, three intentions: the plain one, the one that commits, and the
   one that is barely there. Every button in the app is one of these - a
   fourth style invented on a page is how a toolbar ends up looking assembled
   rather than designed.

   inline-flex rather than the default inline-block, so a button holding an
   icon and a word centres both instead of sitting them on the text baseline
   with a gap underneath. */
.btn {
  appearance:none; cursor:pointer; font:inherit; font-size:13px; font-weight:500;
  display:inline-flex; align-items:center; justify-content:center; gap:7px;
  border:1px solid var(--line); background:var(--surface); color:var(--ink);
  border-radius:var(--radius-sm); padding:0 14px; min-height:36px;
  box-shadow:var(--shadow-xs); white-space:nowrap;
}
/* Hover moves the surface, not the border colour. An accent border on hover
   made every button in a toolbar flash green as the pointer crossed it, which
   competed with the one button that was actually the accent colour. */
.btn:hover { background:var(--sunken); border-color:var(--faint); }
.btn:active { transform:translateY(1px); box-shadow:none; }
.btn.primary {
  background:var(--accent); border-color:var(--accent); color:var(--accent-ink);
  box-shadow:var(--shadow-sm);
}
.btn.primary:hover { background:var(--accent-hover); border-color:var(--accent-hover); }
.btn:disabled, .btn.primary:disabled {
  opacity:.45; cursor:not-allowed; box-shadow:none; transform:none;
  background:var(--sunken); border-color:var(--line); color:var(--muted);
}
.btn.primary:disabled { background:var(--accent); border-color:var(--accent); color:var(--accent-ink); }
.btn.ghost {
  border:0; background:none; box-shadow:none; color:var(--muted);
  align-self:center; min-height:0; padding:6px 10px;
}
.btn.ghost:hover { background:var(--sunken); color:var(--ink); }

/* Headings are set tighter than body text and one notch heavier. At 17px the
   difference between 600 and 650 is small on its own; across a page of them it
   is the difference between a document and an application. */
h2 { font-size:17px; font-weight:650; margin:0 0 4px; letter-spacing:-.015em; }
h3 { letter-spacing:-.01em; }
/* The one thing the width cap on .wrap was doing that was worth keeping.
   A page's opening sentence is prose, and with the cap gone it would run the
   whole width of the screen - 1,880px on this laptop, around 220 characters a
   line, which is past the point where the eye loses its place coming back to
   the start.

   1300px is deliberately NOT a fresh opinion about line length: it is what
   these lines measured inside the old 1340px page, so nothing on any existing
   screen moves. It stops the change making prose worse; it does not set out
   to make it better, which would be a different decision and not this one. */
.lead {
  color:var(--muted); margin:0 0 18px; font-size:13px; max-width:1300px;
}

.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:11px; }
/* The accent bar was on the left of every tile, which meant a row of five
   figures came with five green stripes and the one that mattered had no way to
   stand out. The colour moves to the figure itself, where it is carrying
   meaning, and the tile goes quiet. */
.tile {
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--radius); padding:14px 16px; box-shadow:var(--shadow-xs);
}
.tile .n { font-size:26px; font-weight:650; line-height:1.1; letter-spacing:-.025em; }
.tile .l {
  font-size:10.5px; font-family:"IBM Plex Mono", ui-monospace, monospace;
  letter-spacing:.09em; text-transform:uppercase; color:var(--faint); margin-top:4px;
}

.panel {
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--radius); padding:17px 19px; margin-top:16px;
  box-shadow:var(--shadow-xs);
}
.kv { display:grid; grid-template-columns:auto 1fr; gap:7px 16px; font-size:13px; }
.kv dt { color:var(--faint); }
.kv dd { margin:0; }

.pill { display:inline-block; font-size:11px; font-family:"IBM Plex Mono", ui-monospace, monospace; padding:2px 8px; border-radius:var(--radius-pill); background:var(--sunken); color:var(--muted); }
.pill.on { background:var(--ok-bg); color:var(--ok); }
.pill.off { background:var(--bad-bg); color:var(--bad); }
/* Part-made-up: neither good news nor bad, so neither green nor red -
   30 of 45 minutes is simply where the obligation has got to. */
.pill.part { background:var(--sunken); color:var(--muted); border:1px solid var(--line); }
/* A FACT, NOT A STATE. The date and slot on a row in the reschedule queue is
   a pill because it is a small thing set apart, not because it is good news
   or bad - so it stays the neutral pair and takes the same outline as .part.

   It is written down rather than left to fall through to the bare .pill,
   which is what it did: the outline is the difference between "this pill is
   telling you WHEN" and "this pill is telling you HOW IT WENT", in a queue
   where both sit on the same row. */
.pill.when { background:var(--sunken); color:var(--muted); border:1px solid var(--line); }

@media (max-width: 760px) {
  .wrap { padding:16px 12px 48px; }
  /* Not overflow-x:auto. The bar moves what does not fit into the More menu,
     and a scrolling bar puts that button off the right-hand edge - which is
     exactly how it became unreachable once before. */
  .appnav-inner { gap:10px; padding:0 12px; }
}

/* A month deliberately reopened: not wrong, not settled either. */
.pill.wait { background:var(--wait-bg); color:var(--wait); }
/* USED SINCE BEFORE THESE EXISTED, and unstyled the whole time. `pill ok` is
   what Settings, Billing puts on a month that has gone to the accountant and
   `pill warn` is what it puts on one that was reopened - both of which read
   as plain grey, so the two states that most need telling apart looked
   identical. Nothing throws for an unknown class; the pill simply renders in
   the default colour and nobody can say what is missing.

   `ok` is deliberately the same pair as `on` rather than a new green: they
   mean the same thing in different words, and two greens a shade apart is
   worse than one. */
.pill.ok { background:var(--ok-bg); color:var(--ok); }
.pill.warn { background:var(--wait-bg); color:var(--wait); }

/* Two buttons in a table cell that must not wrap onto two lines. */
.nowrap { white-space:nowrap; }

/* How many parent messages are waiting, on the Messages tab.
   A count, not a dot: "3 waiting" and "one came in while you were on the
   telephone" are different amounts of hurry, and a plain dot says neither.
   Inline rather than absolutely positioned, because the nav link is a flex row
   and a floating badge over a bar that reflows on every resize is a badge that
   ends up over the next word along. */
.wadot {
  display:inline-flex; align-items:center; justify-content:center;
  min-width:17px; height:17px; margin-left:6px; padding:0 5px;
  border-radius:var(--radius-sm); background:var(--bad); color:#fff;
  font-size:11px; font-weight:700; line-height:1;
  /* A ring in the bar's OWN colour, so the badge reads as a disc sitting on
     the bar rather than a red smudge touching the word beside it. --surface
     because that is what .appnav is painted with; this said var(--panel),
     which is not a token this stylesheet defines - and one bad colour voids
     the whole declaration, so there was no ring at all. */
  box-shadow:0 0 0 2px var(--surface);
}
.navmenu-item .wadot { margin-left:auto; }

/* WHAT IS WAITING ON YOU - approvals to decide, items sent back to correct,
   reschedules to decide (0268). The unread dot's shape, deliberately, and not
   its colour: red in this bar already means "a parent has written", and this
   is a different call on the same eye. Amber is --wait, the app's own word for
   "not decided yet", with near-black on it in both themes - white on the light
   theme's amber is too faint to read at eleven pixels. */
.navcount {
  display:inline-flex; align-items:center; justify-content:center;
  min-width:17px; height:17px; margin-left:6px; padding:0 5px;
  border-radius:var(--radius-sm); background:var(--wait); color:#1B1305;
  font-size:11px; font-weight:700; line-height:1;
  box-shadow:0 0 0 2px var(--surface);
}
.navmenu-item .navcount, .drawitem .navcount, .drawhead .navcount { margin-left:auto; }
/* On the burger it is a corner badge: the button is an icon and nothing
   else, so an inline badge would push the icon off centre. */
.burger { position:relative; }
.burger .navcount {
  position:absolute; top:-4px; right:-6px; margin:0;
  box-shadow:0 0 0 2px var(--ground);
}
