/* ==========================================================================
   The shell's own chrome — which is now almost nothing.

   There was a nav bar here with Diary / Programme / Safety in it. It has gone:
   landing on /diary showed that bar on an otherwise empty page for the three
   seconds the diary took to paint, and it read as a second launcher. The
   launcher is a screen of this same document now, and it is the only way
   across. Each section carries its own way back to it.

   Everything else on the page belongs to a module and is scoped to it — see
   scripts/build-shell.lib.mjs.
   ========================================================================== */
.tsr-main{min-height:100vh}
.tsr-module[hidden]{display:none}

/* --- ONE TOP BAR ----------------------------------------------------------

   Every screen wore its own: the diary's was sage with dark text, the
   programme had a white strip with a back arrow, safety had a white bar, Team
   & dev had the kit's default. Same app, four headers.

   There is one now, and it is Safety's — navy ground, white text, the mark on
   a white rounded square so a dark logo still reads on a dark bar.

   THE FIRST PASS AT THIS ONLY PAINTED THEM.

   Colour, text colour and a border, and each bar kept the geometry it was
   written with: 56px on the diary, 60 on three others, 108 on the programme
   because its tab row is inside its header; 16px of side padding on three,
   18 on two, and 11 top and bottom on Safety alone. Flick between two screens
   and the logo lands somewhere different each time — which is what "they are
   all different" meant, and painting them the same colour did not touch it.

   So the height and the padding are set here too, in one place, and
   `--tsr-bar-h` is the number. Everything else about a bar — what is in the
   middle of it, what buttons are on it — stays each screen's own business.

   Not `position`: the diary's is fixed (with padding-top on the module to
   match), Safety's is static inside its own scrolling column, the rest are
   sticky. All three keep the bar at the top of the screen and only one module
   is ever on screen, so the difference cannot be seen — and changing it would
   move every scroll offset in the diary for no visible gain.

   The extra `body` is not decoration: a module's own CSS is inline and
   therefore later, so at equal specificity the module wins. These have to be
   heavier to land at all. */
:root{ --tsr-bar-h:60px; }

body #mod-launcher .lx-bar,
body #mod-diary .topbar,
body #mod-programme header.top .top-in,
body #mod-safety .topbar,
body #mod-team .topbar,
body #mod-expenses .topbar,
body #mod-settings .sx-bar{
  background:var(--topbar-bg);
  color:#fff;
  border-bottom:1px solid rgba(255,255,255,.10);
  box-sizing:border-box;
  height:var(--tsr-bar-h);
  min-height:var(--tsr-bar-h);
  padding:0 16px;
  gap:12px;
}
/* The diary's is a grid, not a flex row — its middle column is the centred job
   title — so it gets the same numbers without being told how to lay itself
   out. `--topbar-h` is its own token and everything else in that module
   measures from it: the settings menu's offset, the sticky move bar, and the
   padding-top that makes room for a fixed bar. Setting it here moves all of
   them together, which is why the bar is not given a bare height instead. */
body #mod-diary{ --topbar-h:var(--tsr-bar-h); }
/* And the job name is the part that gives way.
 *
 * `1fr auto 1fr` sizes the middle column to the whole job name however narrow
 * the window is, so on a phone a long one ran straight out under the buttons.
 * It was already doing that; the account circle made the right group 38px
 * wider and turned "nearly" into "visibly". The title's own ellipsis never
 * fired, because text-overflow does nothing on a flex container whose text is
 * an anonymous item.
 *
 * The two groups take what they need and the name takes the rest, cut with an
 * ellipsis when there is not enough. It is centred between the groups rather
 * than in the bar — a few pixels off when the right group is wide, against a
 * job name that used to disappear under a button. */
body #mod-diary .topbar{grid-template-columns:auto minmax(0,1fr) auto}
body #mod-diary .topbar .tb-title{
  display:block;justify-self:stretch;text-align:center;
  line-height:var(--tsr-bar-h);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
/* What a phone does to this bar is under ONE BAR ON A PHONE, further down,
   with every other bar's. */

/* Everything inside inherits the white, including text a module set to its own
   ink back when its bar was pale. */
body #mod-diary .topbar .tb-title,
body #mod-diary .topbar .tb-name,
body #mod-safety .topbar .tb-title,
body #mod-safety .topbar .tb-title .t,
body #mod-safety .topbar .tb-title .s,
body #mod-team .topbar h1,
body #mod-programme header.top .top-in .chip{color:#fff}
body #mod-safety .topbar .tb-title .s{color:#B9C7BC}
body #mod-safety .topbar .icon-btn,
body #mod-programme header.top .top-in .ghost{color:#fff;border-color:rgba(255,255,255,.22)}
body #mod-programme header.top .top-in label.chip{color:#fff}

/* --- the programme's tab row is not part of its bar -----------------------

   Its header held two rows: the brand row, and Master / Job programme /
   Call-up / … underneath. Both were navy, so the bar read as 108px tall on a
   laptop and 164 on a phone, against 60 everywhere else — the single biggest
   reason the five did not match.

   The tabs have not moved and nothing about them is hidden. They have stopped
   being ON the bar: the navy stops at 60px, and the tabs sit on the page
   below it, which is where a tab strip belongs and what makes this screen's
   bar the same object as the other four.

   `.top-in` carries the bar treatment (with the group at the top of this
   file); `header.top` is now just the two of them stacked. */
body #mod-programme header.top{
  background:var(--page);
  border-bottom:0;
  padding:0;
}
body #mod-programme header.top .top-in{flex-wrap:nowrap}
/* ...and the programme's own `.brand` rule stops laying the shell's brand out.

   The port hands the shell's brand block this module's `brand` class so it
   keeps the `margin-right:auto` that pushes the rest of the header to the
   right. It was taking the other three declarations with it, and
   `#mod-programme .brand` (1,1,0) beats `.tsr-brand` (0,1,0), so
   `align-items:baseline` won: the shell's brand is a 30px logo tile beside a
   two-line wordmark, and a baseline drawn between those puts the logo above
   the line the bell, gear and account circle sit on and the words below it.
   That is the "logo is high, the text is low" on this bar and no other.

   Only `margin-right:auto` is wanted from that rule, so this restores the
   other three. The source keeps its own `.brand` block untouched, because
   _programme/Index.html still has to run as a standalone Apps Script web app
   where `.brand` wraps its own two spans. */
body #mod-programme header.top .top-in .tsr-brand{
  display:inline-flex;
  align-items:center;
  gap:10px;
}
body #mod-programme header.top nav.tabs{
  margin:0;
  padding:0 16px;
  background:var(--page);
  border-bottom:1px solid var(--border);
}
/* Dark-on-pale again, which is what they were written as. The active one used
   to fill itself with the PAGE colour to sit proud of the navy; on the page
   that is invisible, so the underline does the work instead. */
body #mod-programme header.top nav.tabs button{color:var(--muted);opacity:1;background:transparent}
body #mod-programme header.top nav.tabs button:hover{color:var(--ink)}
body #mod-programme header.top nav.tabs button[aria-current="true"]{
  color:var(--ink);background:transparent;border-bottom-color:var(--link);border-radius:0;
}

/* Safety's mark, on a computer too — and only once.

   Safety carries the brand twice: once in its top bar and once at the head of
   its sidebar, which is the same block. Above 861px that sidebar is pinned
   open, so both are on screen at once, four inches apart.

   The one in the TOP BAR used to be the one hidden. That was the wrong way
   round: it left Safety as the one screen out of five whose bar had no logo
   and no wordmark, which is the whole of what the five bars are meant to have
   in common. The sidebar's is a heading on a panel; the bar's is the bar.

   So on a wide screen the sidebar's head goes instead and the bar keeps its
   mark. Nothing is lost with it — the head holds the brand and nothing else,
   and the way home it carried is the same control, still there in the bar. On
   a phone the sidebar is a drawer that is closed most of the time, the two are
   never both on screen, and it keeps its heading.

   `order:-1` puts the bar's in front of the ☰ without moving it in the markup,
   where its own script looks the ☰ up in the position it has always had. */
body #mod-safety .topbar .tsr-brand{order:-1}
@media (min-width:861px){
  body #mod-safety .sb-head{display:none}
}

/* --- the mark, the wordmark, and the way home -----------------------------
   The logo IS the home button. Generated by scripts/chrome.mjs so every screen
   gets the same markup; `data-goto` is what the shell listens for. */
.tsr-brand{
  display:inline-flex;align-items:center;gap:10px;
  padding:4px 8px 4px 4px;border-radius:10px;
  border:1px solid transparent;background:none;cursor:pointer;
  color:#fff;text-decoration:none;flex:0 0 auto;
  -webkit-tap-highlight-color:transparent;
}
.tsr-brand:hover{background:var(--topbtn-bg)}
.tsr-brand:focus-visible{outline:2.5px solid var(--sage-300);outline-offset:2px}
.tsr-mark{
  width:30px;height:30px;border-radius:7px;flex:0 0 30px;
  background:#fff;display:grid;place-items:center;padding:3px;
}
.tsr-mark img{width:100%;height:100%;object-fit:contain;display:block}
.tsr-words{display:flex;flex-direction:column;min-width:0;line-height:1.15}
.tsr-name{
  font-family:var(--font-display);font-weight:700;font-size:14px;
  letter-spacing:.02em;text-transform:uppercase;color:#fff;
}
.tsr-sub{
  font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:#94A79A;margin-top:1px;
}
@media (max-width:520px){
  /* The wordmark goes, the mark stays. A phone's bar has no room for both. */
  .tsr-words{display:none}
  .tsr-brand{padding:4px}
}

/* --- the account circle, and the menu behind it ---------------------------

   The right-hand end of all five bars. It was one screen's control with the
   word "Account" beside it; it is every screen's now, and it is a circle
   only — a label that is one word wide on one screen and absent on four is
   the sort of difference that makes two bars read as two designs, and the
   circle is the same width whoever is signed in.

   The initials come from `--tsr-initials`, which the shell sets on the
   document once. Drawing them in CSS rather than writing them into the
   element is what survives the diary rebuilding its whole bar on every
   render(). */
.tsr-account{
  margin-left:auto;flex:0 0 auto;
  display:inline-grid;place-items:center;
  width:38px;height:38px;padding:0;
  background:transparent;border:1px solid transparent;border-radius:999px;
  color:#fff;font:inherit;cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.tsr-account:hover{background:var(--topbtn-bg)}

/*
 * The notifications bell, immediately left of the settings gear.
 *
 * It takes over the margin-left:auto that pushes the right-hand cluster to the
 * end of the bar, and hands the gear a zero -- the same dance the gear and the
 * circle already do, one control further left. The third rule is the one worth
 * reading twice: when the gear is hidden, which is every crew member's bar, an
 * adjacent-sibling selector still MATCHES it, so the pair can be reached
 * through it. Without that rule the circle would keep an auto of its own, the
 * two autos would split the free space, and the bell would sit stranded in the
 * middle of the bar.
 *
 * Nothing here is positioned. test/shell.test.js pins the complete list of
 * pinned elements in this stylesheet at two, and the panel below is a
 * .tsr-menu with a modifier rather than a fixed box of its own precisely so
 * that list does not have to grow.
 */
.tsr-bell{
  position:relative;
  margin-left:auto;flex:0 0 auto;
  display:inline-grid;place-items:center;
  width:38px;height:38px;padding:0;
  background:transparent;border:1px solid transparent;border-radius:999px;
  color:#fff;font:inherit;cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.tsr-bell:not([hidden]) + .tsr-gear{margin-left:0}
.tsr-bell:not([hidden]) + .tsr-gear[hidden] + .tsr-account{margin-left:0}
/* And the case where the gear is not hidden but ABSENT: the Settings bar, which
   carries none (2.9.9). Neither rule above can reach the circle through an
   element that is not in the document, so it kept an auto of its own and the
   two autos split the bar between them -- the bell stranded dead centre and the
   circle at the end. Exactly the failure the comment above describes, arrived at
   the one way that comment did not cover. */
.tsr-bell:not([hidden]) + .tsr-account{margin-left:0}
.tsr-bell svg{width:19px;height:19px;display:block}
.tsr-bell:hover{background:var(--topbtn-bg)}
.tsr-bell:focus-visible{outline:2.5px solid var(--sage-300);outline-offset:2px}
.tsr-bell[hidden]{display:none}

/* The count. A figure for what wants doing now; for anything merely coming up
   it is the same dot with no number in it, which is why the width is a minimum
   rather than a size. */
.tsr-bell-count{
  position:absolute;top:4px;right:4px;
  min-width:16px;height:16px;padding:0 4px;
  display:grid;place-items:center;
  background:var(--danger-500);color:#fff;
  border:2px solid var(--topbar-bg,#1b2a3a);border-radius:999px;
  font-family:var(--font-ui);font-size:10px;font-weight:700;line-height:1;
}
.tsr-bell-count--quiet{
  min-width:0;width:10px;height:10px;padding:0;
  background:var(--sage-300);
}
.tsr-bell-count[hidden]{display:none}

/*
 * The settings gear, immediately left of the account circle.
 *
 * It takes the margin-left:auto that pushes the pair to the right-hand end,
 * and hands the circle a zero so the two sit together as one cluster rather
 * than being split by two competing autos. The :not([hidden]) guard is the
 * part that matters: a hidden element still matches an adjacent sibling
 * selector, so without it a crew member's bar -- gear hidden -- would lose the
 * push entirely and the circle would sit against the wordmark.
 */
.tsr-gear{
  margin-left:auto;flex:0 0 auto;
  display:inline-grid;place-items:center;
  width:38px;height:38px;padding:0;
  background:transparent;border:1px solid transparent;border-radius:999px;
  color:#fff;cursor:pointer;text-decoration:none;
  -webkit-tap-highlight-color:transparent;
}
.tsr-gear:not([hidden]) + .tsr-account{margin-left:0}
.tsr-gear svg{width:19px;height:19px;display:block}
.tsr-gear:hover{background:var(--topbtn-bg)}
.tsr-gear.on{background:var(--topbtn-bg)}
.tsr-gear:focus-visible{outline:2.5px solid var(--sage-300);outline-offset:2px}
.tsr-gear[hidden]{display:none}
/* The settings menu is the account menu's twin -- same box, same shadow, same
   placement helper -- so the two controls beside each other behave alike. Only
   the width differs: these are section names, not one-word actions. */
.tsr-setmenu{min-width:216px}
.tsr-account:focus-visible{outline:2.5px solid var(--sage-300);outline-offset:2px}
.tsr-avatar{
  width:30px;height:30px;border-radius:999px;
  background:var(--sage-500);color:#fff;
  display:grid;place-items:center;
  font-family:var(--font-ui);font-size:12px;font-weight:600;letter-spacing:.02em;
}
.tsr-avatar::after{content:var(--tsr-initials,"")}

/*
 * The diary's chip rides inside the bar's right-hand group, because that bar
 * is a three-column grid whose middle column is the centred job title and a
 * fourth child would open a fourth column. `margin-left:auto` inside that
 * group would push it away from the buttons it belongs with.
 */
body #mod-diary .topbar .right .tsr-account{margin-left:0}
/* And the bell, for the same reason and one control earlier. `.tsr-bell` is
   what carries the `margin-left:auto` that pushes the bell/gear/circle group
   to the end of a bar; inside this group there is nothing to push away from,
   and on a phone -- where 2.11.3 briefly let `.right` fill the whole
   column -- that auto opened a gap between the Xero wordmark and the bell
   that no other bar has. The gap is what Ben saw. Zero here means the
   cluster is one evenly spaced row at every width, whatever the column does. */
body #mod-diary .topbar .right .tsr-bell{margin-left:0}
/*
 * --- ONE BAR ON A PHONE (2.17.5) -------------------------------------------
 *
 * M, 3 Oct 2026, six screenshots off his phone: "the top bar on mobile is not
 * consistent". Measured at 400px there were three bars:
 *
 *   Launcher, Programme, Team, Settings   60 high, 16 at the edges, 12 between
 *   Safety                                60 high, 10 at the edges,  7 between
 *   Diary, Jobs, Xero                     84 high and TWO ROWS, 34px controls
 *
 * so the mark sat in one of two places and the controls in one of three. The
 * Diary's second row was built on 12 Sep for five tabs that would not fit on
 * one; 2.17.0 took the tabs away and left the row holding four controls that
 * fit beside the mark with room to spare.
 *
 * His picks: one row everywhere, Safety's tighter spacing everywhere, and the
 * middle of the bar names the screen when no job or form is open.
 *
 * So this is the whole of what a phone changes, for every bar at once:
 *
 *   - the edges and the gaps (the same selector list as the shared rule at the
 *     top of this file, so a bar cannot be in one and not the other)
 *   - the middle: the job (Diary), the form (Safety) or `.tsr-title`, the
 *     screen's own name, which is only drawn here because 520px is where the
 *     words beside the mark stop being drawn
 *
 * Nothing shrinks. The round controls are 38px on every bar at every width;
 * the 34 and 32 the Diary's had were the tab row's price, and it has gone.
 */
.tsr-title{display:none}
@media (min-width:521px){
  /* The Diary's middle is its own element. When it holds a screen's name
     rather than a job's it is a phone's, like every other `.tsr-title`. */
  body #mod-diary .topbar .tb-title.tsr-title{display:none}
}
@media (max-width:520px){
  body #mod-launcher .lx-bar,
  body #mod-diary .topbar,
  body #mod-programme header.top .top-in,
  body #mod-safety .topbar,
  body #mod-team .topbar,
  body #mod-expenses .topbar,
  body #mod-settings .sx-bar{padding:0 10px;gap:7px}

  .tsr-title{display:block;flex:1 1 0;min-width:0;text-align:center}
  .tsr-title,
  body #mod-diary .topbar .tb-title,
  body #mod-safety .topbar .tb-title .t{
    font-family:'Archivo',var(--font-display);font-weight:600;font-size:15px;
    line-height:1.2;color:#fff;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }

  /* The Diary's own cluster, spaced as the bar is. Its one button of its own
     (Xero, Leave or Diary) is as wide as what is in it: the module's phone
     rule squares every button on this bar to 34px, which fits the Xero mark
     and cut the word "Diary" off at both ends. */
  body #mod-diary .topbar .right{gap:7px}
  /* An empty "Saved" is still a flex item, and a flex item costs a gap. */
  body #mod-diary .topbar .right .savestate:empty{display:none}
  body #mod-diary .topbar .right .iconbtn.wordmark{width:auto;min-width:34px;margin-left:0;padding:0 7px}
}
@media (max-width:379px){
  body #mod-launcher .lx-bar,
  body #mod-diary .topbar,
  body #mod-programme header.top .top-in,
  body #mod-safety .topbar,
  body #mod-team .topbar,
  body #mod-expenses .topbar,
  body #mod-settings .sx-bar{padding:0 8px;gap:5px}
  body #mod-diary .topbar .right{gap:5px}
}

/* One menu, at document level, for all five chips. Positioned from the button
   it was opened under: inside the bar it would be clipped by the first
   ancestor that hides its overflow, and Safety's bar has one. */
.tsr-menu{
  position:fixed;z-index:200;
  min-width:240px;padding:8px;
  color:var(--color-text);
  background:var(--color-surface);
  border:1px solid var(--color-border);
  border-radius:14px;
  box-shadow:var(--shadow-lg);
}
.tsr-menu[hidden]{display:none}
.tsr-menu-head{padding:12px;border-bottom:1px solid var(--color-border);margin-bottom:8px}
.tsr-menu-head strong{display:block;font-size:14px}
.tsr-menu-head span{font-size:12px;color:var(--color-text-muted);word-break:break-all}
.tsr-menu-item{
  display:flex;align-items:center;gap:12px;
  width:100%;padding:12px;
  background:none;border:0;border-radius:9px;
  color:var(--color-text);font:inherit;font-size:14px;
  text-align:left;text-decoration:none;cursor:pointer;
}
.tsr-menu-item:hover{background:var(--color-surface-sunken)}
.tsr-menu-item--danger{color:var(--danger-500)}
.tsr-menu-label{
  padding:10px 12px 4px;font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--color-text-muted);
}
/*
 * The notifications panel.
 *
 * A .tsr-menu with a modifier, so it inherits the box, the shadow and — the
 * part that matters — the position:fixed that shell.test.js counts. Wider than
 * the other two because these are sentences rather than one-word actions, and
 * capped in height because a workbook nobody has tidied for a month can
 * legitimately have twenty-five things in it.
 */
.tsr-notifmenu{
  min-width:min(360px,calc(100vw - 16px));
  /* Capped as well as floored. Without a maximum the box grows to the
     longest sentence in it, which on a laptop was a panel half the width
     of the screen for six short lines -- and which line was longest
     changed every time the feed did. A fixed ceiling wraps the detail to
     two lines, the way it already wraps on a phone. */
  max-width:min(460px,calc(100vw - 16px));
  max-height:min(70vh,520px);overflow-y:auto;
}
/*
 * The view-as bar.
 *
 * A .tsr-menu with a modifier, so it inherits the box, the shadow and the
 * position:fixed that shell.test.js counts -- there is nothing positional
 * here, and there must not be: everything that places it is set inline by
 * buildViewAsBar, the way the other two panels are placed.
 *
 * Deliberately NOT drawn in the error colour. A message in red is a claim that
 * something is wrong, and viewing as another level is the app working as
 * designed -- what it needs is to be impossible to miss, not alarming.
 */
.tsr-viewas{
  display:flex;align-items:center;gap:14px;
  min-width:0;padding:10px 10px 10px 16px;
  border-width:2px;
  font-size:13.5px;line-height:1.3;
}
.tsr-viewas-text{color:var(--color-text-muted);white-space:nowrap}
.tsr-viewas-level{color:var(--color-text);font-weight:600}
.tsr-viewas-out{
  padding:8px 14px;
  background:var(--color-text);color:var(--color-surface);
  border:0;border-radius:9px;
  font:inherit;font-size:13px;font-weight:600;
  cursor:pointer;white-space:nowrap;
}
.tsr-viewas-out:disabled{opacity:.6;cursor:default}
@media (hover:hover){
  .tsr-viewas-out:hover{opacity:.85}
}
.tsr-viewas-out:focus-visible{outline:2.5px solid var(--sage-300);outline-offset:2px}
/* A phone has no room for the sentence beside the button. */
@media (max-width:420px){
  .tsr-viewas{gap:10px;padding-left:12px}
  .tsr-viewas-text{white-space:normal}
}

/* The row and its cross sit side by side, and the row keeps every rule it
   had. `stretch` so the cross is a full-height target rather than a 14px one:
   this is pressed with a thumb, on a phone, in a ute. */
.tsr-notif-row{display:flex;align-items:stretch}
.tsr-notif-row .tsr-notif{flex:1;min-width:0}
.tsr-notif-clear{
  flex:0 0 auto;width:34px;border:0;background:none;cursor:pointer;
  display:flex;align-items:flex-start;justify-content:center;padding:11px 0 0;
  color:var(--color-text-muted);border-radius:8px;
}
@media (hover:hover){
  /* Wrapped, or a phone leaves the last one pressed lit like a stuck key. */
  .tsr-notif-clear:hover{background:var(--color-surface-sunken);color:var(--color-text)}
}
.tsr-notif-clear:focus-visible{outline:2.5px solid var(--sage-300);outline-offset:-2px}
.tsr-notif{align-items:flex-start;gap:10px}
.tsr-notif-text{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
.tsr-notif-title{font-weight:600;line-height:1.3}
.tsr-notif-detail{font-size:12.5px;line-height:1.35;color:var(--color-text-muted)}
.tsr-notif-meta{
  flex:0 0 auto;align-self:flex-start;
  font-size:11px;color:var(--color-text-muted);white-space:nowrap;padding-top:2px;
}
/* The level, as a colour rather than a word. Three of them, and the same three
   the server ranks by — a fourth colour here would be a fourth opinion. */
.tsr-notif-dot{
  flex:0 0 auto;width:8px;height:8px;margin-top:6px;border-radius:999px;
  background:var(--color-text-muted);
}
.tsr-notif-dot[data-level="now"]{background:var(--danger-500)}
.tsr-notif-dot[data-level="soon"]{background:var(--sage-500)}
.tsr-notif-empty{
  margin:0;padding:12px;
  font-size:13px;color:var(--color-text-muted);
}

/* Three states, all visible at once. A dropdown would hide two of the three
   answers behind a click, for a choice with three answers. */
/* Two across, and the long one on a row of its own underneath (2.13.2).
   "Match my device" is three words and will not sit beside Light and Dark at
   a third of a menu's width; as its own row it reads as the sentence it is. */
.tsr-theme{display:grid;grid-template-columns:1fr 1fr;gap:4px;padding:0 12px 8px}
.tsr-theme button[data-theme-set="system"]{grid-column:1 / -1}
.tsr-theme button{
  padding:7px 4px;
  background:var(--color-surface);border:1px solid var(--color-border);border-radius:8px;
  color:var(--color-text-muted);font:inherit;font-size:13px;cursor:pointer;
}
.tsr-theme button:hover{color:var(--color-text)}
.tsr-theme button.on{
  background:var(--color-primary);border-color:var(--color-primary);color:var(--color-on-primary);
}

/* --- a screen's own settings, beside the circle --------------------------

   Two rules were here, placing Safety's own `#setBtn` immediately left of
   the account circle. That button has gone and no screen has settings of its
   own any more: the account circle carries what is true everywhere, and the
   gear beside it goes to the site-wide Settings screen.

   Nothing replaces them. `.tb-title{flex:1}` in Safety's own sheet is what
   pushes the right-hand cluster to the end of the bar, and the shell's
   `.tsr-bell / .tsr-gear / .tsr-account` chain already zeroes the margins of
   whichever of the three follows another. If a screen grows settings of its
   own again, it goes back in the same spot. */

/* --- too many things do not fit in one bar on a phone ------------------------

   On a form, Safety's bar carries the mark, Back, the save chip, the site
   pin, the bell, the gear and the account circle. At 390px that was 440px of
   content in a 390px window (seven things now, and one fewer since the
   screen's own settings button went), every child is
   `flex:0 0 auto`, and a flex row that cannot wrap and will not shrink does
   not report a problem: it pushes the LAST child off the end. The last child
   is the account circle, which is lock, appearance, PIN and sign out -- so
   none of those was reachable on a phone while a form was open.

   The spacing is every bar's on a phone now (2.17.5, ONE BAR ON A PHONE
   above). The other thing that gave way was the Saved chip's label, then the
   chip itself below 380px -- and the chip is gone altogether as of 2.17.10
   (M, 3 Oct 2026: "remove the save cloud icon"), so its two rules went with
   it. The circles keep their size: shrinking them is how a round control
   becomes an oval, and they are already the smallest thing anybody has to
   hit. */

/* --- a module that would not start ---------------------------------------- */
.tsr-fail{max-width:60ch;margin:48px auto;padding:20px 22px;background:var(--card-bg);
  border:1px solid var(--color-border);border-left:3px solid var(--danger-500);border-radius:12px}
.tsr-fail h2{font-family:var(--font-display);font-size:18px;margin:0 0 8px}
.tsr-fail p{margin:0;color:var(--color-text-muted);font-size:14px}

/* The moment between opening a screen and its code arriving (2.11.17).

   One line, inside that module's own subtree, taken away as soon as the file
   lands -- the markup underneath it is already in the document, so this sits
   on top of a screen that is half there rather than replacing it. Usually
   invisible: the file is warmed at idle and comes from the cache. */
/* ONE loading mark for the whole app (M, 5 Oct 2026: "check the loading icons
   and placements for each page. are they all the same?" -- they were not:
   four different rings, a skeleton, and three sections with nothing).

   .tsr-loader is the logo drawing itself, 48px. It is a background image, not
   inline SVG, so a module's markup carries no ids of its own and the page
   stays small; the drawing and its timing live in /assets/loader.svg, and
   loader-dark.svg is the same file in colours that show on a dark page.

   .tsr-wait is where it sits: centred in the content area, the words under
   the mark. A section that has its own wrapper (.lx-wait, .sx-wait, .loading)
   keeps it and matches these numbers. Reduced motion is honoured inside the
   SVG: nothing moves and the whole mark shows. */
.tsr-loader{display:block;flex:none;width:48px;height:48px;
  background:url(/assets/loader.svg) center/contain no-repeat}
[data-theme="dark"] .tsr-loader{background-image:url(/assets/loader-dark.svg)}
.tsr-wait{display:grid;place-items:center;align-content:center;gap:12px;min-height:50vh;
  padding:0 16px;color:var(--color-text-muted);font-size:14px;line-height:1.4;text-align:center}
.tsr-wait[hidden]{display:none}

/* THE MARK IN THE TOP BAR, WORKING (M, 5 Oct 2026: "change them to be the top
   bar icon animates"). While something is being saved or sent, the logo on
   its white tile draws itself -- outer line, Y, short lines, inner cube, the
   same order as the loading mark -- and when the work is done it finishes the
   loop it is on and is the still logo again. Nothing else on the screen
   moves, and no "Saving" or "Saved" box is needed.

   Tesseract.working() in tesseract-shell.js puts the drawing in and takes it
   out. It is inline SVG here, not loader.svg as a background, because a
   background image keeps its own clock and this has to start at the first
   line every time. The colours are the logo's own: the tile is white in both
   themes. With reduced motion on, nothing is drawn and the tile dims instead. */
.tsr-mark.tsr-working img{display:none}
.tsr-mark-anim{display:block;width:100%;height:100%}
.tsr-mark-anim path{fill:none;stroke-width:3.952}
.tsr-mark-anim .s{stroke:#66725A}
.tsr-mark-anim .n{stroke:#000032}
.tsr-mark-anim .d{stroke-dasharray:1 1.1;stroke-dashoffset:0}
.tsr-mark-anim .sq{stroke-linecap:square}
.tsr-mark-anim .hx{animation:tsr-mk-hx 1.98s cubic-bezier(.6,0,.3,1) infinite}
.tsr-mark-anim .y{animation:tsr-mk-y 1.98s cubic-bezier(.6,0,.3,1) infinite}
.tsr-mark-anim .sh{animation:tsr-mk-sh 1.98s cubic-bezier(.6,0,.3,1) infinite}
.tsr-mark-anim .nv{animation:tsr-mk-nv 1.98s cubic-bezier(.6,0,.3,1) infinite}
@keyframes tsr-mk-hx{0%{stroke-dashoffset:1.02}30%,100%{stroke-dashoffset:0}}
@keyframes tsr-mk-y{0%,30%{stroke-dashoffset:1.02}46%,100%{stroke-dashoffset:0}}
@keyframes tsr-mk-sh{0%,46%{stroke-dashoffset:1.02}58%,100%{stroke-dashoffset:0}}
@keyframes tsr-mk-nv{0%,58%{stroke-dashoffset:1.02}82%,100%{stroke-dashoffset:0}}
@media (prefers-reduced-motion:reduce){ .tsr-mark.tsr-working{opacity:.55} }
/* The diary's small inline ring (.spin) is the loading mark too, 16px, beside
   the words it belongs to. Its light colours are set in the diary's own
   styles; this is the same swap .tsr-loader makes for a dark page. */
[data-theme="dark"] #mod-diary .spin{background-image:url(/assets/loader-dark.svg)}

/* The programme brought its own Dark toggle. The element stays in the DOM
   because its own code looks it up on load and removing it would throw; the
   shell owns theming, so it is hidden rather than deleted. */
#mod-programme #themeBtn{display:none}

/* --- the lock ---------------------------------------------------------------

   WHY IT IS HERE AND NOT IN A MODULE.

   The half-hour idle timeout is a property of the LOGIN, not of any one
   screen. It used to live in the diary — the diary's script armed the watcher
   for the whole document and drew the PIN screen into `#screen`, which sits
   inside `#mod-diary` and is hidden on every other screen. So on the launcher,
   on Safety and on Team & dev the lock came down and NOTHING APPEARED: the
   session stayed live behind a screen nobody could see. The app looked like it
   never locked at all.

   It is a fixed overlay at document level now, over whichever screen you were
   on, drawn by the shell — the same reason .tsr-menu is here rather than in
   five copies.

   z-index above .tsr-menu (200): the account menu must not float over the
   lock, and a menu left open when the timer trips would do exactly that. */
.tsr-lock{
  position:fixed;inset:0;z-index:900;
  display:grid;place-items:center;
  padding:20px;
  background:var(--color-bg);
  overflow-y:auto;
}
.tsr-lock[hidden]{display:none}
.tsr-lock-box{
  width:100%;max-width:380px;
  padding:24px 22px;
  background:var(--card-bg);
  border:1px solid var(--color-border);
  border-radius:14px;
  box-shadow:var(--shadow-lg);
}
.tsr-lock-brand{display:flex;align-items:center;gap:12px;margin-bottom:16px}
.tsr-lock-brand img{width:38px;height:38px}
.tsr-lock-brand h1{font-family:var(--font-display);font-size:19px;font-weight:700;margin:0}
.tsr-lock-brand span{display:block;font-size:12.5px;color:var(--color-text-muted)}
.tsr-lock-sub{margin:0 0 16px;font-size:13.5px;color:var(--color-text-muted);line-height:1.5}
.tsr-lock label{display:block;margin-bottom:6px;font-size:12.5px;font-weight:600}
.tsr-lock input{
  width:100%;padding:11px 12px;margin-bottom:14px;
  background:var(--color-surface);
  border:1px solid var(--color-border);border-radius:9px;
  color:var(--color-text);font:inherit;font-size:16px;
}
.tsr-lock input:focus-visible{outline:2.5px solid var(--color-focus);outline-offset:1px}
/* The unlock box on a desktop only ever holds four digits, so it is spaced
   out and centred -- a keypad's worth of input, not a password box. The PHONE
   has no input at all; see the PIN pad below. */
.tsr-lock input.tsr-lock-pin{font-size:22px;letter-spacing:6px;text-align:center}
.tsr-lock-btn{
  width:100%;padding:12px;
  background:var(--color-primary);border:1px solid var(--color-primary);border-radius:9px;
  color:var(--color-on-primary);font:inherit;font-size:15px;font-weight:600;cursor:pointer;
}
.tsr-lock-btn:disabled{opacity:.6;cursor:default}
.tsr-lock-btn--sec{
  margin-top:8px;
  background:none;border-color:var(--color-border);color:var(--color-text-muted);font-weight:500;
}
.tsr-lock-alt{display:block;margin-top:14px;text-align:center;font-size:12.5px;
  color:var(--color-text-muted)}
.tsr-lock-err{margin-top:12px;min-height:1em;font-size:13px;color:var(--danger-500)}

/* ------------------------------------------------------------ the PIN pad
   The unlock screen is a keypad and nothing else -- no card, no logo, no
   paragraph. It borrows .tsr-lock-box for its width and then takes the card
   off it, because the box is what centres the thing and .tsr-lock is already
   the only fixed element allowed to be here.

   Sizes are worked from a thumb, not from the type ramp: 66px keys on a 3
   column grid is a little over 100px wide on a 390px phone, which is what an
   operating system's own passcode pad gives itself. */
.tsr-pinpad{
  max-width:340px;
  padding:8px 0 22px;
  background:none;border:0;box-shadow:none;
  /* A PIN with the same digit twice (1123) is a double-tap, and iOS Safari
     zooms the page on a double-tap. manipulation turns that off across the
     whole pad -- keys and the gaps between them -- and keeps pinch-zoom. */
  touch-action:manipulation;
}
.tsr-pin-title{
  margin:0 0 28px;
  font-family:var(--font-display);font-size:27px;font-weight:700;
  text-align:center;color:var(--color-text);
}
.tsr-pin-dots{display:flex;justify-content:center;gap:26px}
.tsr-pin-dot{
  width:11px;height:11px;border-radius:50%;
  background:var(--color-border);
  transition:background .12s ease,transform .12s ease;
}
.tsr-pin-dot.on{background:var(--color-text);transform:scale(1.15)}
.tsr-pinpad .tsr-lock-err{margin-top:14px;text-align:center}
.tsr-pin-keys{
  display:grid;grid-template-columns:repeat(3,1fr);
  gap:4px;margin-top:20px;
}
.tsr-pin-key{
  display:flex;align-items:center;justify-content:center;
  min-height:66px;padding:0;
  background:none;border:0;border-radius:16px;
  color:var(--color-text);
  font-family:var(--font-display);font-size:31px;font-weight:700;
  cursor:pointer;user-select:none;-webkit-tap-highlight-color:transparent;
}
/* Hover only where there is a pointer. A phone that keeps the last :hover
   until the next tap would leave one key lit like a stuck digit. */
@media (hover:hover){.tsr-pin-key:hover{background:var(--color-surface)}}
.tsr-pin-key:active{background:var(--color-border)}
.tsr-pin-key:focus-visible{outline:2.5px solid var(--color-focus);outline-offset:-4px}
/* Clear and delete are words, not digits, and must not compete with them. */
.tsr-pin-word{
  font-family:var(--font-body,inherit);font-size:15px;font-weight:500;
  color:var(--color-text-muted);
}
/* While the four digits are with the server. pointer-events off is the point:
   a fifth tap during the round trip would be a digit for a PIN nobody is
   typing any more. */
.tsr-pin-busy{opacity:.45;pointer-events:none}
.tsr-pinpad .tsr-lock-alt{margin-top:20px}
/* THE PIN BEING CHECKED (M, 9 Oct 2026: "add loader to pin unlocking"). On the
   keypad the four dots give way to the logo drawing itself -- the top bar's
   drawing and timing, at 40px. The row keeps the dots' height and the drawing
   spills over it, so nothing under it moves. On a dark page it takes
   loader-dark.svg's colours. On the desktop screen the Unlock button carries
   it small, in the colour of its words, the same as Sign in. */
.tsr-pin-dots[hidden]{display:none}
.tsr-pin-wait{display:flex;justify-content:center;align-items:center;height:11px;overflow:visible}
.tsr-pin-wait .tsr-mark-anim{flex:none;width:40px;height:40px}
[data-theme="dark"] .tsr-pin-wait .tsr-mark-anim .s{stroke:#9DBE92}
[data-theme="dark"] .tsr-pin-wait .tsr-mark-anim .n{stroke:#B9C4E0}
.tsr-lock-btn .tsr-mark-anim{display:inline-block;width:18px;height:18px;vertical-align:-4px;margin-right:8px}
.tsr-lock-btn .tsr-mark-anim .s,.tsr-lock-btn .tsr-mark-anim .n{stroke:currentColor}

/* ------------------------------------------------ the banners question
   Same overlay as the PIN lock, deliberately: shell.test.js pins the list of
   position:fixed rules in this file at exactly two, and a third full-screen
   box would either join that list or be a second way of doing the same thing.
   It borrows .tsr-lock whole and only overrides what is different -- which is
   a smaller, quieter second answer, because "Not now" is a real answer and
   must not look like the one being recommended. */
.tsr-askpush .tsr-lock-skip{
  display:block;width:100%;margin-top:10px;padding:9px;
  background:none;border:0;border-radius:9px;
  color:var(--color-text-muted);font:inherit;font-size:13px;cursor:pointer;
}
.tsr-askpush .tsr-lock-skip:hover{background:var(--color-surface)}
.tsr-askpush .tsr-askpush-said{margin:12px 0 0;min-height:1em}
.tsr-askpush .tsr-askpush-said.bad{color:var(--danger-500)}


/* --- errors and reports -------------------------------------------------
 *
 * Both borrow .tsr-lock for the full-screen part and override NOTHING
 * positional, which is the rule shell.test.js enforces: exactly two pinned
 * elements in this file, .tsr-menu and .tsr-lock, and a third full-screen
 * thing is a class on one of them rather than a third position:fixed.
 *
 * The severity is a border and a heading colour, never the whole box. A
 * `blocked` -- a validation, a role gate -- is an ANSWER, and painting it in
 * the same red as a failure is a claim that something is wrong when nothing
 * is. That was the CHECK-in-the-red-box bug, and this is where it is stopped.
 */
.tsr-error-box,.tsr-report-box{max-width:420px}
/* Over the policy board and the induction review (z-index 1000): a save that
   fails there says so in this box, and one drawn under them is unseen (#43). */
.tsr-lock.tsr-error,.tsr-lock.tsr-report{z-index:1100}
.tsr-error-box{border-top:4px solid var(--color-border)}
.tsr-error-box[data-severity="failed"]{border-top-color:var(--danger-500)}
.tsr-error-box[data-severity="check"]{border-top-color:var(--warning)}
.tsr-error-head{font-family:var(--font-display);font-size:18px;font-weight:700;margin:0 0 10px}
.tsr-error-box[data-severity="failed"] .tsr-error-head{color:var(--danger-500)}
.tsr-error-box[data-severity="check"] .tsr-error-head{color:var(--warning)}
.tsr-error-says{margin:0 0 12px;font-size:14.5px;line-height:1.5}
.tsr-error-where{margin:0 0 12px;font-size:12.5px;color:var(--color-text-muted);line-height:1.45}
/* The code and the ref are the whole point of the box existing, so they are
   selectable and they are monospace -- somebody is going to read this one
   character at a time off a phone in the sun. */
.tsr-error-code{
  margin:0 0 16px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:12.5px;letter-spacing:.04em;color:var(--color-text-muted);
  user-select:all;-webkit-user-select:all;
}
.tsr-error-acts{display:flex;gap:10px;flex-wrap:wrap}
.tsr-error-acts .tsr-lock-btn{flex:1 1 auto;margin-top:0}
.tsr-report-text{
  width:100%;box-sizing:border-box;padding:10px 12px;font:inherit;font-size:15px;
  border:1.5px solid var(--color-border);border-radius:8px;
  background:var(--color-surface);color:var(--color-text);resize:vertical;
}
.tsr-report-text:focus-visible{outline:2.5px solid var(--color-focus);outline-offset:1px}
.tsr-report-note{margin:10px 0 14px}

/* The optional photo. Deliberately plain: a file input styled to look like a
   button is a file input people cannot tell has taken their picture, and the
   thumbnail underneath is what says it has. */
.tsr-report-shot{display:block;margin:12px 0 4px;font-size:13px}
.tsr-report-shot small{opacity:.7}
.tsr-report-file{display:block;width:100%;font:inherit;font-size:13px}
.tsr-report-thumb{margin:8px 0 0;font-size:12px;opacity:.85}
.tsr-report-thumb img{display:block;max-width:100%;max-height:160px;border-radius:6px}

/* Scan site QR (2.16.10) -- full-screen camera over the app. Pinned inline in
   tesseract-shell.js, not here: only .tsr-menu and .tsr-lock pin in this file. */
.tsr-menu-scan{font-weight:600}
.tsr-menu-fence{padding:4px 16px 10px;font-size:13px;line-height:1.35;color:var(--color-text-muted,#5c5c58)}
.tsr-menu-fence-on{font:inherit;font-weight:600;border:0;background:none;padding:0;color:var(--color-primary,#2f5d50);text-decoration:underline;cursor:pointer}
.tsr-scan{background:#000;color:#fff;display:flex;flex-direction:column}
.tsr-scan-bar{display:flex;align-items:center;justify-content:space-between;padding:calc(env(safe-area-inset-top,0px) + 12px) 16px 12px}
.tsr-scan-bar strong{font-size:17px}
.tsr-scan-x{background:none;border:0;color:#fff;font-size:22px;width:44px;height:44px;cursor:pointer}
.tsr-scan-view{position:relative;flex:1;overflow:hidden}
.tsr-scan-view video{width:100%;height:100%;object-fit:cover;display:block}
.tsr-scan-frame{position:absolute;left:50%;top:50%;width:64vmin;height:64vmin;transform:translate(-50%,-50%);border:3px solid rgba(255,255,255,.85);border-radius:16px;box-shadow:0 0 0 100vmax rgba(0,0,0,.35)}
.tsr-scan-msg{margin:0;padding:14px 16px 4px;font-size:16px;text-align:center}

/* Tesseract policies (27 Sep 2026) -- its own screen, opened from the account
   circle on every bar. Pinned inline in tesseract-shell.js, like the scanner:
   only .tsr-menu and .tsr-lock pin in this file. Drawn to look like Safety's
   old policies page, which is what everybody already knows it as: a board per
   category, a coloured stripe per row, open a row to read it. */
.tsr-pol{background:var(--color-bg,var(--page));color:var(--color-text);display:flex;flex-direction:column}
.tsr-pol-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:calc(env(safe-area-inset-top,0px) + 10px) 16px 10px;background:var(--topbar-bg);color:#fff}
.tsr-pol-bar strong{display:block;font-family:var(--font-display);font-size:16px;font-weight:600}
.tsr-pol-sub{display:block;font-size:12px;opacity:.8}
.tsr-pol-x{background:none;border:0;color:#fff;font-size:22px;width:44px;height:44px;cursor:pointer}
.tsr-pol-scroll{flex:1;overflow:auto;-webkit-overflow-scrolling:touch}
.tsr-pol-wrap{max-width:760px;margin:0 auto;padding:24px 16px calc(env(safe-area-inset-bottom,0px) + 48px)}
.tsr-pol-h{font-family:var(--font-display);font-size:27px;font-weight:700;margin:0 0 16px}
.tsr-pol-msg{color:var(--color-text-muted);font-size:14.5px}
.tsr-pol-msg.bad{color:var(--danger-500)}
.tsr-pol-note{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;
  border:1px solid var(--warning);border-radius:12px;padding:13px 15px;margin:0 0 20px}
.tsr-pol-note.ok{border-color:var(--sage-300);color:var(--sage-500)}
.tsr-pol .tsr-lock-btn{width:auto;padding:9px 14px;font-size:14px}
.tsr-pol-board{border:1px solid var(--color-border);border-radius:14px;overflow:hidden;
  background:var(--color-surface);margin:0 0 20px}
.tsr-pol-cat{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--color-text-muted);
  font-weight:600;padding:12px 15px 9px;border-bottom:1px solid var(--color-border);background:var(--color-surface-sunken)}
.tsr-pol-item{border-bottom:1px solid var(--color-border)}
.tsr-pol-item:last-child{border-bottom:0}
.tsr-pol-row{display:flex;align-items:center;gap:12px;width:100%;padding:13px 15px 13px 19px;position:relative;
  background:none;border:0;font:inherit;color:inherit;text-align:left;cursor:pointer}
.tsr-pol-row:focus-visible{outline:2.5px solid var(--color-focus);outline-offset:-2px}
.tsr-pol-stripe{position:absolute;left:0;top:0;bottom:0;width:4px;background:var(--color-border)}
.tsr-pol-stripe.ok{background:var(--sage-500)}
.tsr-pol-stripe.find{background:var(--danger-500)}
.tsr-pol-main{flex:1;min-width:0}
.tsr-pol-nm{font-weight:600;font-size:14.5px;line-height:1.3;display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.tsr-pol-chip{font-size:10.5px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;padding:2px 8px;
  border-radius:99px;border:1px solid var(--color-border);color:var(--color-text-muted);white-space:nowrap}
.tsr-pol-st{display:block;font-size:12.5px;margin-top:2px;color:var(--color-text-muted)}
.tsr-pol-st.ok{color:var(--sage-500)}
.tsr-pol-st.find{color:var(--danger-500);font-weight:600}
.tsr-pol-chev{color:var(--color-text-muted);font-size:14px}
.tsr-pol-text{padding:4px 15px 18px 19px;font-size:14.5px;line-height:1.55}
.tsr-pol-text .pol-p{margin:0 0 10px}
.tsr-pol-text .pol-list{margin:0 0 12px;padding-left:20px}
.tsr-pol-text .pol-list li{margin:0 0 6px}
.tsr-pol-src{font-size:12.5px;color:var(--color-text-muted);margin:12px 0}

/* Address autocomplete (2.16.18) -- Tesseract.addressAutocomplete. Fixed and
   on <body> so a modal cannot clip it; above modals and the error box. */
.tsr-ac {
  position: fixed;
  z-index: 100000;
  margin: 0;
  padding: 4px 0;
  list-style: none;
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  box-shadow: var(--shadow-lg);
  font-family: var(--font-ui);
  max-height: 60vh;
  overflow-y: auto;
}
.tsr-ac[hidden] { display: none; }
.tsr-ac li {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 9px 12px;
  min-height: 40px;
  justify-content: center;
  cursor: pointer;
}
.tsr-ac li.on, .tsr-ac li:hover { background: var(--color-surface-sunken); }
.tsr-ac-main { font-size: 14px; }
.tsr-ac-sub { font-size: 12px; color: var(--color-text-muted); }

/* ON-SCREEN BACK BUTTONS ARE AN iPHONE'S (2.17.4, M: "any back button should
   only be for iPhone"). A desktop has the browser's Back and Android has the
   phone's; only an iPhone or iPad has neither. The shell puts `tsr-ios` on
   <html> for those two -- see Tesseract.onIOS -- and every module marks its
   back button `tsr-iosback`. !important because a module's own CSS is inline,
   later, and gives most of these buttons a display of their own. */
html:not(.tsr-ios) .tsr-iosback { display: none !important; }
