:root {
  --fg: #e8e8e8;
  --dim: #8a8a8a;
  --bg: #16181c;
  --line: #2c2f36;
  --ok: #6bbf59;
  --warn: #d9a441;

  /* The content column, and the sidebar it sits beside above 48rem. One column
     of one width at every size: a phone gets all of it there is, and everything
     wider stops at --col, which is where a line of this text stops being
     comfortable to read. The two lengths are named because the body width is
     derived from them rather than chosen separately — see the media query. */
  --col: 34rem;
  --side: 11rem;
  --gap: 2rem;
}

* { box-sizing: border-box; }

body {
  margin: 0 auto;
  padding: 1rem;
  /* Clears the fixed tab bar. The inset is 0 unless the viewport covers it, so
     it costs nothing on the devices that do not have a home indicator. */
  padding-bottom: calc(4.5rem + env(safe-area-inset-bottom));
  max-width: var(--col);
  font: 16px/1.5 system-ui, sans-serif;
  color: var(--fg);
  background: var(--bg);
}

/* The 1.5rem is space *between* sections, not space above a heading: it is
   asked for by the element in front of it, so a heading that opens a screen —
   Trend's "Weight", Settings' first fold — sits at the top the way Today's day
   row does, while two headings down the same screen keep the full gap. */
h1, h2 { font-size: 1rem; font-weight: 600; margin: 0 0 .5rem; }
* + :is(h1, h2) { margin-top: 1.5rem; }

/* Same idea one level down, for the screens that open on something other than a
   heading: Lifts leads with a paragraph, whose own margin would otherwise push
   it off the top edge. Only the leading element is trimmed — everything after
   it spaces itself normally. */
main > section > :first-child,
main > section > :first-child > :first-child { margin-top: 0; }

/* A tab bar across the bottom, where a thumb already is. Fixed rather than
   sticky: the nav is the first thing in the document, and a sticky element
   still occupies its place in the flow, so it would reserve a strip at the top
   and slide down into view — which is not the same shape at all. Fixed takes it
   out of the flow entirely and body's padding-bottom pays for the space.

   It is first in the DOM and last on the screen, and that is the right way
   round: source order is what a screen reader and the tab key follow, and the
   navigation belongs at the front of both. */
nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
  display: flex;
  align-items: stretch;
  /* The inset is what lets the filled tab read as a shape sitting in the bar
     rather than a block wedged into its corner. */
  gap: .25rem;
  padding: .35rem .5rem calc(.35rem + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--bg);
}
/* Equal shares of the width, so the targets are uniform and the one you want is
   always in the same place. Borderless: four bordered boxes edge to edge read as
   a toolbar rather than a bar you are somewhere in. */
nav button {
  flex: 1;
  min-width: 0;
  padding: .7rem .25rem;
  border: 0;
  border-radius: 4px;
  background: none;
  font-size: .9rem;
}
/* The filled row is the marker at both sizes — it is the same rule, not two
   that happen to match. Colour alone is a weak thing to have to find, and a
   4 px radius is what every other box in the app is drawn with. */
nav button[aria-current="true"] {
  color: var(--fg);
  border-color: var(--fg);
  background: #1f2229;
}

/* Sits above the bar rather than in it: it is empty almost always, and giving
   it a share of a four-way split would either squeeze the tabs or leave a hole
   where nothing ever appears. Chip-styled because it now floats over content. */
#sync {
  position: absolute;
  right: .5rem;
  bottom: 100%;
  margin-bottom: .5rem;
  padding: .1rem .45rem;
  color: var(--dim);
  font-size: .8rem;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 3px;
}
#sync:empty { display: none; }

#today header {
  display: flex;
  align-items: center;
  gap: .5rem;
}
#today header h2 {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: .4rem;
  margin: 0;
}

/* Bare, unlike the arrows either side of it: a bordered box next to the title
   reads as a third navigation button, which is the bulk this is avoiding. The
   padding is the tap target and the only reason it is not simply the glyph. */
#day-lock {
  padding: .25rem;
  border: 0;
  background: none;
  line-height: 0;
  color: var(--dim);
}
#day-lock svg { fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linecap: round; }
#day-lock .open { display: none; }

/* Armed. Amber rather than green: this is a state to notice and leave, not a
   thing done correctly. */
#day-lock[aria-pressed="true"] { color: var(--warn); }
#day-lock[aria-pressed="true"] .shut { display: none; }
#day-lock[aria-pressed="true"] .open { display: inline; }

/* A grouping element and nothing else — the browser's own border and padding
   would draw a box around half the screen. */
#day-edit { margin: 0; padding: 0; border: 0; min-width: 0; }

/* A fieldset establishes a block formatting context, so the first form's top
   margin no longer collapses into the note above it and everything below moved
   down. Dropping it restores the spacing exactly: the wrapper is here to group
   controls, and it may not cost the layout a pixel. */
#day-edit > :first-child { margin-top: 0; }

/* The dimming is the whole "you are in the past" cue, so there is no separate
   banner or tint: the region that cannot be typed into is visibly the region
   that cannot be typed into. */
#day-edit[disabled] { opacity: .55; }

label {
  display: block;
  margin: .5rem 0;
  color: var(--dim);
  font-size: .85rem;
}
label input, label select { display: block; width: 100%; margin-top: .15rem; }

/* Checkbox rows read the other way round: control first, then the text, both on
   one line. The box is sized up because the whole row is a phone tap target. */
label.check { display: flex; align-items: center; gap: .5rem; color: var(--fg); }
label.check input {
  display: inline-block;
  width: 1.1rem;
  height: 1.1rem;
  margin: 0;
  flex: 0 0 auto;
  accent-color: var(--ok);
}

/* Dimmed but present: the weight box and the logging tick on future days are
   disabled in render() rather than hidden, so the layout holds still between
   today and tomorrow. Same opacity as a locked day. */
#weight-form.dimmed, #logging-row.dimmed, #logging-note.dimmed { opacity: .55; }

input, select, textarea, button {
  font: inherit;
  color: var(--fg);
  background: #1f2229;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: .45rem .6rem;
}
textarea { width: 100%; resize: vertical; }
button { cursor: pointer; color: var(--dim); }
button:hover { color: var(--fg); }

/* A number and the unit it is in, kept together. Every dimensioned field in the
   app is one of these: the unit is a sibling of the box rather than its
   placeholder, because a placeholder is only on screen while the field is
   empty — the one time nobody needs telling what the units are. */
.field { display: flex; align-items: center; gap: .3rem; min-width: 0; }
.field input { min-width: 0; text-align: right; }
.unit { color: var(--dim); font-size: .8rem; white-space: nowrap; }

/* Spinners are ~20 px of arrows per box that no finger has ever used, on values
   chosen in plate increments and whole calories rather than by stepping. The
   step attributes stay: they still govern what the field accepts. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }

form { margin: .75rem 0; }
#weight-form { display: flex; gap: .4rem; }
#weight-form .field { flex: 1; }
#weight-form input { flex: 1; }

/* Description on its own line, then the two numbers and the two buttons.
   Wrapping keeps the buttons usable on a narrow phone instead of crushing the
   number fields to nothing. */
#food-row { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .4rem; }
#food-row .field { flex: 1 1 7rem; }
#food-row .field input { flex: 1 1 3rem; }
#food-row button { flex: 0 0 auto; }
#f-add { color: var(--fg); border-color: var(--fg); }

h3 {
  display: flex;
  margin: 1.25rem 0 .35rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--dim);
}
h3 span {
  margin-left: auto;
  color: var(--fg);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

.bar {
  position: relative;
  height: .55rem;
  border-radius: 3px;
  background: #23262e;
  overflow: hidden;
}
.bar i { position: absolute; top: 0; bottom: 0; }
#p-fill, #k-fill { left: 0; background: var(--dim); }
#p-fill.hit, #k-fill.hit { background: var(--ok); }
#k-fill.over { background: var(--warn); }
/* Target band, drawn over the fill so its edges stay visible in every state. */
#k-band {
  background: rgba(232, 232, 232, .10);
  border-left: 1px solid var(--fg);
  border-right: 1px solid var(--fg);
}

#goals p { margin: .35rem 0 0; font-size: .85rem; }

/* No numbers yet — either still loading or nothing to compute from. Dim rather
   than absent: the layout is final from the first frame, so the arriving data
   changes what the trackers say and never where anything sits. */
#goals.pending h3 span { color: var(--dim); }
#goals.pending .bar { opacity: .5; }

#stats { margin: 1.25rem 0; }

/* uPlot draws axes and grid from JS options (see chart.ts theme()); these rules
   cover the parts it renders as DOM. Everything here is .chart, shared by both
   charts on Trend: both pan and zoom the same way, and touch-action is what
   trades a finger drag over either of them for scrolling the page. */
.chart .u-over { touch-action: none; cursor: grab; }
.chart .u-over:active { cursor: grabbing; }

/* uPlot sizes its root to min-content, which lets the legend widen the whole
   chart. Pinning it to the container keeps both to the same width. */
.chart .uplot { width: 100%; }

/* Live legend doubles as the hover readout: the date and every value at the
   cursor, all on one line. uPlot lays a single-value legend out inline already;
   what it does not do is stop the line reflowing, because each cell is sized by
   whatever it happens to be showing — so "9.9" widening to "10.5" shoves
   everything after it along, and the readout squirms while the cursor sweeps.
   Flex with a fixed width per value slot, wide enough for the longest reading
   that slot can ever hold, pins every label in place: the numbers change and
   nothing moves. */
.chart .u-legend {
  display: flex;
  align-items: baseline;
  gap: .75rem;
  margin-top: .5rem;
  font-size: .75rem;
  text-align: left;
  white-space: nowrap;
}
/* The rows are the flex items, so the tbody the browser inserts has to get out
   of the way between them. */
.chart .u-legend tbody { display: contents; }
.chart .u-legend tr {
  display: flex;
  gap: .3rem;
  min-width: 0;
  margin: 0;
}
/* A series uPlot draws but the readout does not report — the weight chart's
   projection. uPlot builds one legend row per series with no way to opt out, so
   the row is dropped here; chart.ts marks the series with the class. */
.chart .u-legend tr.unlisted { display: none; }
/* The numeric pairs hold their size unconditionally. Left shrinkable they would
   be squeezed along with the date on a narrow screen, which is the one thing
   the fixed widths below exist to prevent. */
.chart .u-legend tr:not(:first-child) { flex: none; }
.chart .u-legend th,
.chart .u-legend td { display: block; padding: 0; }
/* Labels never give up room — they are the fixed half of every pair, and the
   only thing worth squeezing is the date value below. */
.chart .u-legend th { flex: none; color: var(--dim); font-weight: 400; }
.chart .u-legend .u-value { color: var(--fg); font-variant-numeric: tabular-nums; }

/* The date gets a fixed width like everything else — "Wed, 10 Jul" is the
   widest it reads and "Fri, 1 Aug" the narrowest, which is a whole character of
   swing on the one cell that changes on every single point.

   It is still the slot that gives way when the row will not fit, but it gives
   way by shrinking against the container rather than against its own text, so
   the width is decided by the screen and never by which day is under the
   cursor. Ellipsis rather than a wrap, since a second line would move the
   numbers rather than just clip a weekday. */
.chart .u-legend tr:first-child { flex: 0 1 auto; overflow: hidden; }
.chart .u-legend tr:first-child .u-value {
  width: 11ch;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* "70.05 kg", the longest the trend can read at a plausible bodyweight. */
#chart .u-legend tr:not(:first-child) .u-value { width: 8.5ch; }
/* The index is rebased to 100 at the first session on record, and ten weeks of
   novice progress already reads 248 — so four digits and a decimal, which is a
   fifty-fold gain and will not be reached. */
#strength-chart .u-legend tr:not(:first-child) .u-value { width: 6.5ch; }
/* Four digits, a space and "kcal" — "2543 kcal" is the longest either slot can
   read, and the dash it shows on an unlogged day is narrower, which without
   this is the whole of the squirm. */
#calorie-chart .u-legend tr:not(:first-child) .u-value { width: 9.5ch; }
/* Single-exercise mode reads e1RM in kg, where "250.0 kg" overflows the
   6.5ch the index is sized for. The class is toggled with the mode. */
#strength-chart.kg .u-legend tr:not(:first-child) .u-value { width: 8.5ch; }

/* The strength filter. Preset chips wrap exactly like the template chips on
   Lifts: a handful at most, and one tap beats open-scroll-pick on a phone. */
#strength-presets { display: flex; flex-wrap: wrap; gap: .4rem; margin: .35rem 0 .5rem; }
#strength-picker { margin: 0 0 .5rem; }
#strength-picker summary { cursor: pointer; }
/* Checkbox rows. The label is the toggle target and "only" solos the row —
   both padded into thumbs, since the picker is useless if it mistaps. */
#strength-rows .srow {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .3rem 0;
  border-bottom: 1px solid var(--line);
}
#strength-rows label { flex: 1; display: flex; align-items: center; gap: .5rem; }
#strength-rows input[type="checkbox"] { flex: none; width: 1.1rem; height: 1.1rem; }
#strength-rows .only {
  border: 0;
  background: none;
  color: var(--dim);
  padding: .4rem .6rem;
}

#trend-note { margin: .75rem 0 .25rem; }

/* Label and value per row, the same two-column read as a session row. */
#week { margin: .5rem 0 0; }
#week div {
  display: flex;
  gap: .5rem;
  align-items: baseline;
  padding: .4rem 0;
  border-bottom: 1px solid var(--line);
}
#week dt { flex: 0 0 5.5rem; color: var(--dim); font-size: .85rem; }
#week dd { flex: 1; margin: 0; font-variant-numeric: tabular-nums; }
.chart .pts circle { fill: var(--dim); }
.chart .ewma { fill: none; stroke: var(--ok); stroke-width: 1.5; }
.chart text { fill: var(--dim); font-size: 8px; }

ul { list-style: none; padding: 0; margin: 1rem 0; }
li {
  display: flex;
  gap: .5rem;
  align-items: baseline;
  padding: .4rem 0;
  border-bottom: 1px solid var(--line);
}
/* The name is now the description as typed, so it can be a whole sentence. */
li .name { flex: 1; overflow-wrap: anywhere; }
li .macros { color: var(--dim); font-size: .85rem; white-space: nowrap; }
li button { border: 0; background: none; padding: 0 .25rem; }

/* ------------------------------------------------------------------ lifts */

/* The templates. Wrapping chips rather than a select: there are only ever a
   handful, and one tap beats open-scroll-pick on a phone. */
#templates { display: flex; flex-wrap: wrap; gap: .4rem; margin: .35rem 0 1rem; }
.chip.blank { color: var(--dim); }
#templates .chip { border-color: var(--fg); color: var(--fg); }
#templates .chip.blank { border-color: var(--line); color: var(--dim); }

/* The one thing on the screen when a session is open, so it is the only thing
   marked.

   The [hidden] rule is load-bearing, not tidiness: the attribute hides an
   element through a UA rule of the lowest possible specificity, so the
   display below — an id selector — silently beats it and the button stays on
   screen with .hidden = true set on it. Any display here needs this next to
   it. */
#lift-resume[hidden] { display: none; }
#lift-resume {
  display: block;
  width: 100%;
  margin: .5rem 0 1rem;
  text-align: left;
  color: var(--ok);
  border-color: var(--ok);
}

/* Session rows. The whole row is the tap target — a phone has no business
   aiming at a word. */
#sessions li { padding: 0; }
#sessions .row {
  display: flex;
  gap: .5rem;
  align-items: baseline;
  width: 100%;
  padding: .55rem 0;
  border: 0;
  border-radius: 0;
  background: none;
  text-align: left;
}
#sessions .when { color: var(--dim); font-size: .85rem; flex: 0 0 5.5rem; }
#sessions .name { flex: 1; color: var(--fg); overflow-wrap: anywhere; }

#lift header { display: flex; align-items: center; gap: .5rem; margin-bottom: .25rem; }
#lift header input { flex: 1; min-width: 0; }
#lift-when { margin: 0 0 1rem; }

.exercise { margin-bottom: 1.25rem; }
/* The shared h3 is a flex row that never needed aligning, because everywhere
   else it holds two pieces of text of equal height. Here it holds a button
   half again as tall, and the default stretch left the name sitting at the top
   of its own stretched box while the × was centred in its. */
/* At label size it read as a caption over the sets. It is not a label like the
   PROTEIN and CALORIES headings this h3 shares its rules with — those are a
   fixed vocabulary naming a bar, and this is the name you typed. So it reads at
   the size a food item's name does, which is what it is. Only the size needs
   saying: `h3 span` already returns the rest of the label treatment. */
.exercise h3 { align-items: center; margin-bottom: .4rem; font-size: 1rem; }
.exercise h3 .ex-name { margin-left: 0; color: var(--fg); }
/* Borderless like the delete on a food item — the established shape for
   removing a row — but sized to the same box as the set buttons below it, so
   every rightmost control on the screen is one target of one size, lined up in
   one column. It was a 13 px glyph asking for a fingertip. */
.exercise h3 button {
  margin-left: auto;
  border: 0;
  background: none;
  width: 2.5rem;
  padding: .35rem 0;
  font-size: 1.1rem;
  line-height: 1;
}

/* weight × reps, then the one button that either confirms or removes. The
   fields are sized to their contents — three digits and a half — rather than
   sharing the row, which left two identical boxes wide enough to say nothing
   about which was which. */
/* Unlike the wrapping rows on Today, both fields and the action sit on one line
   whatever the width, so these are sized to their contents rather than sharing
   the row — two identical boxes said nothing about which was which. */
.set { display: flex; gap: .3rem; align-items: center; margin-bottom: .35rem; }
.set .field { flex: 0 0 auto; }
.set input.kg { width: 4rem; }
.set input.reps { width: 3.25rem; }
.set .by { color: var(--dim); font-size: .85rem; margin: 0 .1rem; }
.set button { flex: 0 0 2.5rem; margin-left: auto; }

/* Not this session's numbers — last session's, offered as a question. Dimmed
   rather than empty, because the answer is usually "the same again", and a tap
   on ✓ is the whole of saying so. */
.set.ghost input { color: var(--dim); border-style: dashed; }
.set.ghost button { color: var(--ok); border-color: var(--ok); }

/* Adding a set and moving the exercise are both things done to the exercise as a
   whole, so they share the row this already was rather than the h3, which holds
   the name and the one control that destroys it. The arrows take the same 2.5rem
   box as the set buttons above, so the row ends in the one right-hand column the
   rest of the screen keeps to; `+ set` stays dashed, because of the three it is
   the only one that adds anything.

   At either end of the list one arrow is missing rather than dimmed, and `+ set`
   grows into the space it leaves — so on the last exercise the lone ↑ sits in the
   column ↓ holds everywhere else. That shift is the price of not drawing a
   control that would do nothing, and it is the one that was chosen. */
.ex-foot { display: flex; gap: .3rem; }
.add-set { flex: 1; color: var(--dim); border-style: dashed; }
.ex-foot .move { flex: 0 0 2.5rem; }

#l-add-form { display: flex; gap: .4rem; }
#l-add-form input { flex: 1; min-width: 0; }

.datalist-fallback {
  position: absolute;
  z-index: 30;
  margin: -1px 0 0;
  padding: 0;
  list-style: none;
  max-height: 15rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: #1f2229;
  border: 1px solid var(--line);
  border-radius: 0 0 4px 4px;
}
.datalist-fallback li {
  padding: .45rem .6rem;
  cursor: pointer;
  /* A phone taps this; a long name should not silently become another one. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.datalist-fallback li + li { border-top: 1px solid var(--line); }
.datalist-fallback li:active,
.datalist-fallback li:hover,
.datalist-fallback li[aria-selected="true"] { color: var(--bg); background: var(--fg); }

#lift-actions { display: flex; gap: .4rem; margin-top: 1.5rem; }
#l-save { flex: 1; color: var(--fg); border-color: var(--fg); }
#l-save:disabled { color: var(--dim); border-color: var(--line); cursor: default; }
#l-delete { color: var(--warn); }

/* Settings is a stack of folded sections. The heading is the whole hit area, so
   there is nothing to aim at on a phone, and the marker is drawn by us because
   the native triangle sits on the wrong side and cannot be styled. */
#settings details { border-bottom: 1px solid var(--line); }
#settings details[open] { padding-bottom: .75rem; }
#settings summary {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .75rem 0;
  cursor: pointer;
  list-style: none;
}
/* The leading fold is trimmed the way leading headings are: its .75rem is space
   between folds, and on the first one there is nothing above to be spaced from.
   Load-bearing on desktop, where a fold that opened .75rem low would be the
   deepest baseline in the row and drag the whole sidebar down to meet it — so
   the one thing meant to hold still between tabs would move on this one. */
#settings form > details:first-child > summary { padding-top: 0; }
#settings summary::-webkit-details-marker { display: none; }
#settings summary h2 { margin: 0; flex: 1; }
#settings summary::after {
  content: "›";
  color: var(--dim);
  transition: transform .15s;
}
#settings details[open] > summary::after { transform: rotate(90deg); }
#settings form { margin: 0; }

/* The one action on the screen, below every section and clear of the last
   border. Marked like #f-add, the other button that commits something. */
#settings-save {
  margin-top: 1.25rem;
  color: var(--fg);
  border-color: var(--fg);
}

/* Three ways of handing the same document somewhere else, so they sit as one
   row of equals rather than one action and two afterthoughts. Share is hidden
   where the browser has no share sheet, which is most desktops. */
#export-actions { display: flex; gap: .4rem; margin: .75rem 0 .5rem; }
#export-actions button { flex: 1; }
#export-actions button:disabled { color: var(--line); cursor: default; }

/* Small and monospaced: this is there to be glanced at and copied out of, not
   read. The rows attribute keeps it a preview rather than a wall of csv. */
#x-text {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .7rem;
  line-height: 1.35;
  color: var(--dim);
}

/* One line of explanation per setting, behind an ⓘ built by buildTips(). A
   mouse hovers it; a tap toggles .open, which is the same thing for a finger
   that has no hover to give. The bubble hangs off the label rather than the
   button so a long one cannot run off the side of a narrow screen. */
#settings label { position: relative; }
.tip {
  border: 0;
  background: none;
  padding: 0 .25rem;
  font-size: .85rem;
  line-height: 1;
}
.tip.open { color: var(--fg); }
.bubble {
  display: none;
  position: absolute;
  left: 0;
  top: 1.6rem;
  /* Above the tab bar. The last setting on the screen sits right on top of it,
     and half an explanation disappearing under the nav is worse than one
     briefly covering it — the bubble is transient and pointer-events: none, so
     a tap aimed at a tab still gets there. */
  z-index: 20;
  /* It hangs over the input it describes, so it must not be in the way of it.
     Transparent to the pointer, the hover ends the moment the cursor leaves the
     ⓘ itself, the field underneath stays clickable, and a tap on the bubble
     reaches the page and dismisses it like a tap anywhere else. */
  pointer-events: none;
  width: max-content;
  max-width: 100%;
  padding: .45rem .6rem;
  color: var(--fg);
  background: #23262e;
  border: 1px solid var(--line);
  border-radius: 4px;
  font-size: .8rem;
  text-align: left;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .45);
}
/* The trigger is the label, not the ⓘ — a 13 px glyph is a mean thing to ask a
   mouse to find, and the words next to it are what you were already reading.
   The control inside the label is deliberately excluded: the bubble hangs over
   that field, so hovering it has to dismiss rather than sustain the tooltip, or
   there is no way to reach the input past its own explanation. */
@media (hover: hover) {
  #settings label:hover:not(:has(input:hover, select:hover)) .tip { color: var(--fg); }
  #settings label:hover:not(:has(input:hover, select:hover)) .bubble { display: block; }
}
.tip.open .bubble, .tip:focus-visible .bubble { display: block; }

.note, .msg { color: var(--dim); font-size: .85rem; }
.msg:empty { display: none; }
.msg.ok { color: var(--ok); }
.msg.err { color: var(--warn); }

/* -------------------------------------------------------- wider than a phone */

/* Everything above this line is the phone, unchanged and untouched by what
   follows. From 48rem — an iPad in portrait, a laptop, a phone turned sideways —
   the tab bar becomes a sidebar. The column does not change width here: it is
   --col above and below, and the sidebar is what the extra room buys.

   Landscape phones are deliberately included. Vertical space is the scarce one
   there, and a bar across the bottom spends it at exactly the moment there is
   least to spend.

   The HTML is the same either way. It can be, because nav and the sections are
   already siblings: nav takes the first column, and every section is placed in
   the same cell of the second, which they can share because only one of them is
   ever without [hidden]. */
@media (min-width: 48rem) {
  body {
    /* Exactly what the two columns need, so the pair sits centred with nothing
       left over. Wider than this and the sidebar and its column stay put on the
       left while the empty half of the body grows to their right. */
    max-width: calc(var(--side) + var(--gap) + var(--col));
    padding-bottom: 1rem;
  }

  /* Setup sits outside #app, so the column rules below do not reach it, and the
     body is now wide enough for two columns. Back to one. */
  #setup { max-width: var(--col); }

  /* Load-bearing, and the same trap as #lift-resume: [hidden] hides through a
     UA rule of the lowest possible specificity, so the id selector below would
     quietly beat it and show the app behind the setup screen. */
  #app[hidden] { display: none; }
  /* Baseline, not start. Aligning the tops lines up two *boxes*, and a box top
     is not what the eye reads — it reads the row of text, which sits wherever
     that box's padding and font leave it. So Trend's bare "Weight" and Today's
     bordered ‹ button both started level and neither looked level with "Today"
     in the sidebar. Grid baseline alignment lines up the first text baseline of
     each item instead, and nav's first baseline is the Today button's, so the
     tab you are on and the line beside it sit on one line.
     This is why the margin-trimming above is load-bearing: a leading margin now
     drags the section's first baseline down, and the deepest baseline in the row
     is the one everything else moves to meet. */
  #app {
    display: grid;
    grid-template-columns: var(--side) 1fr;
    gap: 0 var(--gap);
    align-items: baseline;
  }

  /* Back into the flow, in the first column, and following the page down. */
  nav {
    position: sticky;
    top: 1rem;
    bottom: auto;
    left: auto;
    right: auto;
    grid-column: 1;
    grid-row: 1;
    flex-direction: column;
    gap: .15rem;
    padding: 0;
    border-top: 0;
    background: none;
  }
  /* Read as a list, so: left-aligned, and at the size of the text it sits
     beside. The fill marking the current one is inherited from the bar — only
     the shape of the row changes here, not what being on it looks like. */
  nav button {
    padding: .45rem .6rem;
    text-align: left;
    font-size: 1rem;
  }

  #sync {
    position: static;
    margin: .75rem 0 0;
    padding: 0;
    border: 0;
    background: none;
  }

  /* All in the one cell, at the one width — the same --col the body is capped
     at below this breakpoint, so nothing about the column changes as the
     sidebar appears. Trend had a wider column of its own for a while, and the
     width was never the problem: the sidebar holds still between tabs, so the
     whole of the difference landed on one moving edge and read as arriving at a
     different page rather than a wider one. */
  #app > section { grid-column: 2; grid-row: 1; max-width: var(--col); }
}
