/*
 * Application styles.
 *
 * One stylesheet rather than one per component, because there is no build step
 * and thirty `<link>` tags would be thirty round trips. It is organised in the
 * order a person meets it: reset, shell, navigation, then each screen's pieces.
 *
 * Nothing here uses a colour literal. Every value comes from tokens.css, which
 * is what makes the theme switch a single attribute on <html>.
 *
 * Two rules run through the whole file:
 *
 *   - Colour is never the only carrier. The current tab has a bar as well as a
 *     tint; the running segment has a stripe as well as a hue; every category
 *     swatch sits beside its own name. A person who cannot distinguish two of
 *     these colours loses nothing.
 *
 *   - Nothing moves without being asked to. There is no entrance animation, no
 *     pulse, no slide. The few transitions are on colour and opacity, they are
 *     short, and they all route through the two motion tokens so the reduced
 *     motion rule can switch them off in one place.
 */

@import url('./tokens.css');
@import url('./themes.css');

/* ── reset ─────────────────────────────────────────────────────────── */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Three factors, all multiplying, none replacing another.

     The browser's own setting is the 100%, and it comes first because somebody
     who has already enlarged text system-wide should not be silently reset to
     our idea of normal. `--text-scale` is the person's setting on this screen.
     `--theme-scale` is the theme's, and Comfort is the only theme that sets it.

     They multiply rather than override so that the two can be held at once:
     choosing Comfort and then asking for larger text again gives larger text
     again. A theme quietly replacing an accessibility preference somebody set
     deliberately would be the wrong behaviour, and with an inline `--text-scale`
     beating any stylesheet it could not have worked anyway. */
  font-size: calc(100% * var(--text-scale, 1) * var(--theme-scale, 1));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}

/* ── touch ─────────────────────────────────────────────────────────── */

/*
 * The small behaviours that separate an application from a page, none of which
 * are visible until somebody uses this with a thumb.
 *
 * The tap highlight is the loudest. A translucent grey rectangle flashes over
 * whatever was touched, in a shape that follows the element's box rather than
 * its rounded corners, and it is the single clearest signal that a thing is a
 * web page. It is removed rather than restyled: every control here already has
 * its own pressed state, so the browser's is a second answer to a question
 * that was answered.
 *
 * `touch-action: manipulation` gives up double-tap-to-zoom on controls, and
 * only on controls. Pinch zoom is untouched — this must not become a way of
 * refusing somebody a larger view — and what it buys is that a quick second
 * tap on a button is a second tap rather than a zoom.
 */
* {
  -webkit-tap-highlight-color: transparent;
}

a,
button,
label,
select,
summary,
[role='button'],
[role='radio'],
[role='switch'] {
  touch-action: manipulation;
}

/*
 * Furniture is not text. Holding a finger on a tab should not select the word
 * under it or raise the link preview iOS offers for anchors — both of which
 * are correct for a document and wrong for a tab bar.
 *
 * Deliberately not applied to the whole application. Activity names, notes and
 * times are the person's own words, and making them unselectable would take
 * away the ability to copy them out for the sake of a small consistency.
 */
.nav__item,
.btn,
.icon-btn,
.switch,
.sheet__close,
.icon-picker__option {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

h1,
h2,
h3,
p,
dl,
dd,
dt,
figure {
  margin: 0;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

hr.rule {
  border: none;
  border-top: 1px solid var(--border);
  margin: var(--space-4) 0;
}

/* A visible focus ring on every interactive element, in a colour that holds
   against both themes. Removing this is the single most common way a web
   application becomes unusable by keyboard. */
:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Available to screen readers, not to eyes.
   Note what this class does *not* do: it leaves an element in the tab order and
   in the accessibility tree. That is right for a live region or a skip link and
   wrong for the file input behind "Restore from a backup", which would then be
   a second, unnamed stop on the way past a button that already does the job.
   That one carries `tabindex="-1"` and `aria-hidden` as well — see
   screens/settings.js. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The skip link, which is `.sr-only` until it is focused. Without the second
   rule it stays invisible when tabbed to, which is the whole point of it. */
a.sr-only:focus,
a.sr-only:focus-visible {
  position: fixed;
  top: var(--space-3);
  left: var(--space-3);
  width: auto;
  height: auto;
  margin: 0;
  padding: var(--space-3) var(--space-4);
  clip: auto;
  clip-path: none;
  background: var(--surface);
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
  color: var(--text);
  z-index: 100;
}

/* ── shell ─────────────────────────────────────────────────────────── */

.app {
  min-height: 100dvh;
  display: grid;
  grid-template-rows: auto 1fr auto;
}

/*
 * The application's own bar: the mark, the name, and nothing else.
 *
 * It exists because identity was the one thing the product did not repeat. Every
 * screen already shares a palette, a type scale and a tab bar, but a person
 * moving from the page about the product into the product itself had no single
 * object in common between the two, and the tab bar changes what it highlights
 * on every screen. This does not change at all, which is the entire point of it.
 *
 * Deliberately quiet: secondary text at the small size, no border, no fill of
 * its own. It is a signature rather than a header — the screen's own title is
 * the largest thing on the screen and has to stay that way, and a bar competing
 * with it would have cost more than the recognition is worth.
 */
.appbar {
  width: 100%;
  max-width: var(--page-max);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  /* The top inset lives here now rather than on `.screen`, because this is what
     is actually against the status bar in an installed copy. */
  padding: calc(var(--space-4) + env(safe-area-inset-top, 0px))
    calc(var(--space-4) + env(safe-area-inset-right, 0px)) 0
    calc(var(--space-4) + env(safe-area-inset-left, 0px));
}

.appbar__mark {
  /* No radius and no shadow: the file arrives with its own rounded tile and is
     transparent outside it. */
  display: block;
  flex: none;
}

.appbar__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  letter-spacing: -0.01em;
  color: var(--text-secondary);
}

/*
 * A grid item's automatic minimum size is its content, so without this one
 * unbreakable string anywhere in the application makes *every* screen wider
 * than the window and the whole app scrolls sideways. That is not a theory:
 * the running clock at the largest text size did exactly this, and the symptom
 * appeared on the page header, three levels away from the cause.
 *
 * With `min-width: 0` the failure is contained to the element that cannot fit,
 * which is a bug you can see and fix rather than one that moves.
 */
main {
  min-width: 0;
}

main:focus {
  /* Focus is moved here after every navigation so a screen reader announces the
     new screen. It is not a control, so it gets no ring. */
  outline: none;
}

/*
 * The safe areas are added here rather than to `.app`, so that the page
 * background still reaches the edges of the glass and only the *text* is kept
 * off the notch and the rounded corners. An inset applied to the frame instead
 * leaves a strip of a different colour along the top of an installed copy,
 * which reads as a rendering fault rather than as a design.
 *
 * All four resolve to zero in a browser tab, so none of this changes anything
 * until the application is installed — which is the point at which
 * `apple-mobile-web-app-status-bar-style: black-translucent` in index.html
 * starts drawing the clock over the first line of the screen.
 */
.screen {
  width: 100%;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--space-4) calc(var(--space-4) + env(safe-area-inset-right, 0px))
    var(--space-6) calc(var(--space-4) + env(safe-area-inset-left, 0px));
}

/*
 * Arriving at a screen.
 *
 * A native application moves when you go somewhere, and stays perfectly still
 * when you change something on the screen you are already on. The class is only
 * added on a navigation, so flicking a switch rebuilds the screen in place
 * without it flinching.
 *
 * The distance is deliberately small. Anything further reads as a page load,
 * which is the thing this is meant to stop it feeling like. Reduced motion is
 * already handled — tokens.css forces every animation to 1ms.
 */
@keyframes screen-in {
  from {
    opacity: 0;
    transform: translateY(0.5rem);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.is-entering {
  animation: screen-in var(--motion) var(--ease);
}

.screen__header {
  margin-bottom: var(--space-5);
}

.screen__title {
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  line-height: var(--leading-tight);
}

.screen__subtitle {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  margin-top: var(--space-1);
}

.screen__footnote {
  margin-top: var(--space-6);
  color: var(--text-tertiary);
  font-size: var(--text-sm);
}

.section {
  margin-top: var(--space-6);
}

.section__title {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
  margin-bottom: var(--space-3);
}

.section__subtitle {
  margin-top: var(--space-5);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-secondary);
}

.section__note {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  margin-bottom: var(--space-3);
}

/* The space inside is φ of the space between: 26px against 16px. Both were 16
   before, which is why a column of cards read as one striped block rather than
   as several separate things — with no difference between the gap and the inset
   there is nothing to say where one card's business ends. */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-5);
  box-shadow: var(--shadow);
}

.card + .card,
.card + .section,
.section + .card {
  margin-top: var(--space-4);
}

.stack > * + * {
  margin-top: var(--space-3);
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.row--end {
  justify-content: flex-end;
  margin-top: var(--space-4);
}

.prose {
  color: var(--text-secondary);
  max-width: 38rem;
}

.prose + .prose {
  margin-top: var(--space-3);
}

.prose-card {
  padding: var(--space-5);
}

/*
 * The first screen, which is the closest thing this application has to a
 * shop window: the only one a person meets before they have agreed to
 * anything, and the only one with no data on it. That second fact is what
 * makes it the one place the brand can be spent. Every other screen has
 * category colour on it, and orange there is already Family and red is
 * already Distracted, so warmth anywhere near the timeline reads as a
 * category rather than as the product.
 *
 * The glow is behind everything and pinned to the top, so it sits under the
 * mark and has faded out well before the prose. `radial-gradient` on the
 * screen itself rather than a painted element, because there is nothing here
 * to make an element out of — it is light, not an object, and an empty div
 * would be one more node for a screen reader to walk past.
 */
/* The one screen with no bar above it, so it takes the inset back. Onboarding is
   the product introducing itself — it already shows the mark at full size and
   names itself in its heading, and a third signature above both would be the
   product saying its own name three times before saying anything else. It is
   also the only screen with the glow, which the bar would have cut a seam
   across. */
.screen--welcome {
  padding-top: calc(var(--space-4) + env(safe-area-inset-top, 0px));
  background:
    radial-gradient(
      120% 62% at 50% 0%,
      color-mix(in srgb, var(--brand-warm) 18%, transparent) 0%,
      color-mix(in srgb, var(--brand-deep) 7%, transparent) 45%,
      transparent 72%
    )
    no-repeat;
}

/* No radius and no box-shadow. The file arrives with its own rounded tile and
   is transparent outside it, so a radius here would clip corners that are
   already cut and a box-shadow would be thrown by the square the drawing sits
   in rather than by the drawing. */
.welcome__mark {
  display: block;
  width: 4.5rem;
  height: 4.5rem;
  margin-bottom: var(--space-4);
}

.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: var(--space-3);
}

/* ── icons ─────────────────────────────────────────────────────────── */

/*
 * Drawn in src/ui/icons.js, and drawn in one colour: `currentColor`. That is
 * the whole reason these are SVG rather than emoji — an icon inherits the
 * category's colour from the tile around it, so a category somebody recolours
 * takes its icon with it, and a dark theme needs no second set.
 */

.icon {
  display: block;
  width: var(--icon-size, 1.5rem);
  height: var(--icon-size, 1.5rem);
  /* Icons live in flex rows beside text that is allowed to wrap. Without this
     the icon is what gives, and a squashed circle reads as a rendering fault
     rather than as a narrow screen. */
  flex: none;
}

/* The mass of the shape. Low enough to sit under the line work rather than
   compete with it, and expressed as opacity rather than a lighter colour so
   that it follows whatever the category is recoloured to. */
.icon__solid {
  fill: currentColor;
  opacity: 0.16;
}

.icon__line {
  fill: none;
  stroke: currentColor;
  /* One weight for the whole set. Changing it here changes every icon, which
     is the point of them all being drawn on the same grid. */
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* An `icon` value that names no drawing — an emoji from a document written
   before the set existed, or a character somebody typed. It keeps the tile and
   the box, so a mixed list still lines up. */
.icon--char {
  display: grid;
  place-items: center;
  width: var(--icon-size, 1.5rem);
  height: var(--icon-size, 1.5rem);
  font-size: calc(var(--icon-size, 1.5rem) * 0.8);
  line-height: 1;
}

/*
 * The tile. A neutral recess with the icon coloured on top of it, rather than a
 * coloured tile with a white icon: the category palette is only fitted to 2.5:1
 * against the surface, which is ample for a shape beside a label and nowhere
 * near enough to carry a knocked-out glyph.
 */
.now__icon,
.activity__icon,
.entry__icon,
.row-item__icon,
.icon-picker__option {
  display: grid;
  place-items: center;
  flex: none;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  color: var(--cat, var(--text-secondary));
}

/* ── choosing an icon ──────────────────────────────────────────────── */

.icon-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--tap-min), 1fr));
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.icon-picker__option {
  --icon-size: 1.5rem;
  aspect-ratio: 1;
  min-height: var(--tap-min);
  border: 1px solid transparent;
  color: var(--text-secondary);
  cursor: pointer;
  transition:
    border-color var(--motion-fast) var(--ease),
    background var(--motion-fast) var(--ease);
}

.icon-picker__option:hover {
  border-color: var(--border-control);
}

/* The chosen one. Marked with a fill *and* an edge, because a single tinted
   background is the first thing lost to a low-quality screen in daylight. */
.icon-picker__option[aria-checked='true'] {
  background: var(--accent-subtle);
  border-color: var(--accent);
  color: var(--accent);
}

/* ── navigation ────────────────────────────────────────────────────── */

/* `hidden` is only `display: none` in the browser's own stylesheet, which any
   `display` here outranks. Without this the attribute would be set, the element
   would stay on screen, and the bar would look hidden to a code reader and be
   fully present to everybody else. */
.nav[hidden],
.appbar[hidden] {
  display: none;
}

.nav {
  position: sticky;
  bottom: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  background: var(--surface);
  border-top: 1px solid var(--border);
  /* Clears the home indicator on iPhones without adding dead space on
     everything else. The side insets keep the outer two tabs off the rounded
     corners in landscape; the bar's own background is left to run under them,
     so the fill still reaches the edge of the glass. */
  padding-bottom: env(safe-area-inset-bottom, 0);
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  z-index: 10;
}

.nav__item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--tap-comfortable);
  padding: var(--space-2) var(--space-1);
  color: var(--text-tertiary);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: color var(--motion-fast) var(--ease);
}

.nav__item[aria-current='page'] {
  color: var(--accent);
}

/* The current tab is marked by a bar as well as by colour, so the state is
   not carried by hue alone. */
.nav__item[aria-current='page']::before {
  content: '';
  position: absolute;
  top: 0;
  width: 2rem;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--accent);
}

/* A drawn icon rather than a text character, so the bar sits at the same height
   on every platform instead of at whatever the system font's metrics for `▤`
   happen to be. Slightly larger than the icons in a list, because this one is
   read at arm's length and at a glance. */
.nav__mark {
  --icon-size: 1.5rem;
  display: flex;
  line-height: 1;
}

.nav__label {
  line-height: 1;
}

/*
 * The same four sections, as a rail, once there is a mouse-sized amount of room.
 *
 * A phone's tab bar stretched across a desktop window is the single clearest
 * tell that something was built for a phone and then merely allowed to open
 * anywhere else: four labels marooned in the middle of a wide strip, a thumb's
 * distance from the bottom of a screen no thumb will ever touch, and the whole
 * of the window's height given to a column of content 44rem wide with the
 * navigation nowhere near it.
 *
 * 60rem rather than a device name, because the question is only whether there is
 * width to spare. A tablet held in landscape has it and gets the rail; the same
 * tablet turned upright does not and gets the bar back.
 *
 * The order in the document does not change, so a keyboard reaches the content
 * first and the sections last, exactly as it does with the bar. That is a
 * mismatch between where the rail is and when it is reached — it is also the
 * arrangement every screen in this application already had, and the skip link
 * at the top of the page is the way past it either way.
 */
@media (min-width: 60rem) {
  .app {
    grid-template-columns: auto 1fr;
    grid-template-rows: 1fr;
  }

  /* Placed rather than flowed. Auto-placement follows the document, which would
     put the rail on the right of the screen it belongs to. */
  main {
    grid-column: 2;
    grid-row: 1;
  }

  .nav {
    grid-column: 1;
    grid-row: 1;
    /* Stuck to the top and as tall as the window, so the rail stays put while
       the screen beside it scrolls. `align-self` is what stops the grid from
       stretching it and quietly defeating the sticky. */
    position: sticky;
    top: 0;
    bottom: auto;
    align-self: start;
    height: 100dvh;
    box-sizing: border-box;
    width: 13rem;
    grid-template-columns: 1fr;
    grid-auto-rows: min-content;
    align-content: start;
    gap: var(--space-1);
    padding: var(--space-5) var(--space-3);
    padding-left: calc(var(--space-3) + env(safe-area-inset-left, 0px));
    border-top: none;
    border-right: 1px solid var(--border);
  }

  .nav__item {
    flex-direction: row;
    justify-content: flex-start;
    gap: var(--space-3);
    min-height: var(--tap-min);
    padding: var(--space-3);
    border-radius: var(--radius);
    font-size: var(--text-sm);
  }

  .nav__item[aria-current='page'] {
    background: var(--accent-subtle);
  }

  /* The marker turns with the rail: a bar along the leading edge rather than
     across the top. It stays because the tinted background is still colour, and
     the point of the marker is to say which section this is without it. */
  .nav__item[aria-current='page']::before {
    top: 50%;
    left: 0;
    width: 3px;
    height: 1.5rem;
    transform: translateY(-50%);
  }

  /* Nothing is sitting at the bottom of the window any more for a toast to
     clear, so it stops hovering above a gap the height of a tab bar. */
  #toasts {
    bottom: var(--space-5);
  }
}

/* ── the running activity ──────────────────────────────────────────── */

/* The running card is filled with the category rather than edged with it.
   Edged, it was a grey card with a coloured hairline and read as one more row
   in a list of rows; the whole screen's job is to answer "what am I doing", and
   the answer was the quietest thing on it. Filled, the answer is visible from
   across a desk, and the colour is doing work rather than decoration.

   Both custom properties arrive inline. `--cat` is the fill and `--cat-ink` is
   what is legible on it, chosen per category, because white on a category
   colour clears 4.5:1 for four of the eleven and fails for the other seven. */
.now--live {
  background: var(--cat, var(--accent));
  border-color: transparent;
  color: var(--cat-ink, #fff);
  /* Lifted a little further than a plain card. It is the only filled thing on
     the screen and the shadow keeps it from looking pasted on. */
  box-shadow: var(--shadow-raised);
}

.now__top {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.now__icon,
.now__stop {
  /* A well in the ink colour rather than a solid chip: on a filled card any
     opaque fill would be a third colour, and there is no third colour that
     works on eleven backgrounds. */
  background: color-mix(in srgb, var(--cat-ink, #fff) 16%, transparent);
  color: inherit;
}

.now__icon {
  --icon-size: 1.5rem;
  flex: none;
  width: 2.75rem;
  height: 2.75rem;
}

.now__stop {
  --icon-size: 1.25rem;
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  /* The one tap that ends a recording, so it is a full target rather than the
     visual size of the glyph. */
  width: var(--tap-min);
  height: var(--tap-min);
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease);
}

.now__stop:hover {
  background: color-mix(in srgb, var(--cat-ink, #fff) 28%, transparent);
}

.now__stop:active {
  background: color-mix(in srgb, var(--cat-ink, #fff) 36%, transparent);
}

/* The label above the name. Held back rather than dimmed with a second colour,
   because there is no second colour that reads as secondary on eleven fills. */
.now__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.75;
}

.now--idle {
  border-left: 4px solid var(--border);
}

/* The live card tints its chip with the ink it is already using. There is no
   ink here, and the inherited `--cat-ink` fallback of white would paint white
   on white, so the idle chip is given a surface of its own. */
.now--idle .now__icon {
  color: var(--text-secondary);
  background: var(--surface-sunken);
}

.now__body {
  /* `min-width: 0` stops a very long activity name from widening the card: it
     may overflow its own line, but it may not push the page sideways. */
  flex: 1 1 auto;
  min-width: 0;
}

.now__name {
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
}

.now--live .now__counter {
  margin-top: var(--space-3);
}

.now__counter {
  font-family: var(--font-numeric);
  /* Tabular figures, so the digits do not shift the layout each second as they
     change width.

     The size is capped against the window as well as set in rem. `3rem` is the
     intent and is what nearly every phone gets; on a 320px screen with the
     in-app text scale turned up it came to 77px, and "0:07:26" in a monospace
     face at 77px is 313px of unbreakable string. The clock is still by some way
     the largest thing on the screen at the capped size — it just stops being
     the reason somebody has to scroll sideways to read their own day. */
  font-size: min(var(--text-clock), 13vw);
  font-variant-numeric: tabular-nums;
  line-height: var(--leading-tight);
  letter-spacing: -0.02em;
}

.now__since {
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

/* Inherited, not `--text-secondary`: on a filled card the secondary grey is
   either invisible or the only dark thing on it, depending on the category. */
.now--live .now__since {
  color: inherit;
  opacity: 0.8;
}

/* ── today so far ──────────────────────────────────────────────────────── */

.today-so-far {
  margin-top: var(--space-5);
}

/* Three abreast and boxed, rather than the bare rows the same figures make on
   Today and Week. Those screens are a report and a figure there is a paragraph;
   here it is a glance, and the boxes are what stop it reading as a second
   heading under the buttons. */
.today-so-far .figures {
  grid-template-columns: repeat(3, 1fr);
}

.today-so-far .figure {
  padding: var(--space-4) var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.today-so-far .figure__value {
  font-family: var(--font-numeric);
  font-size: var(--text-lg);
  /* A third of a phone is not wide enough for "3h 45m" at the size the report
     screens use it, and the wrap put the minutes on their own line under the
     hours, which reads as two numbers rather than one. */
  white-space: nowrap;
}

.today-so-far .figure__label {
  font-size: var(--text-xs);
}

.quality {
  margin-top: var(--space-4);
}

.quality__ask {
  margin-bottom: var(--space-3);
  color: var(--text-secondary);
}

/* ── activity buttons ──────────────────────────────────────────────── */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--space-3);
}

.activity {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  /* Well above the 44px minimum: the specification asks for a control usable
     while walking and one-handed, and this is the one tapped dozens of times a
     day. */
  min-height: 5.5rem;
  padding: var(--space-4);
  /* A wash of the category rather than the bare surface. At 8% it is a tint and
     not a colour — enough that a grid of eight reads as eight different things
     at a glance, nowhere near enough to compete with the filled card above it
     or to change what the label needs to be legible against.

     Mixed into `--surface` rather than laid over it with alpha, so the result
     is opaque and the shared chip inside can still darken against it. */
  background: color-mix(in srgb, var(--cat, transparent) 8%, var(--surface));
  /* White on a near-white page: the fill puts about a hundredth of a contrast
     step between this button and the screen, so the outline is doing all the
     work of saying where one tappable card stops and the next begins. It used
     to be `--border`, which is right for a divider and far too faint for the
     control this application is mostly made of. */
  border: 1px solid var(--border-control);
  border-radius: var(--radius);
  text-align: left;
  transition:
    border-color var(--motion-fast) var(--ease),
    background var(--motion-fast) var(--ease);
}

/* Darker than the resting edge, not lighter. Pointing this at `--border-strong`
   was a darkening only while the resting edge was fainter still; now it would
   make the border retreat under the cursor. */
.activity:hover {
  border-color: var(--text-tertiary);
}

.activity:active {
  background: var(--surface-sunken);
}

/* The running one, marked in the accent rather than in its own category hue.
   The category was the prettier choice and it made the state unfindable for
   half the palette: six of the eleven category colours sit under 3:1 on a light
   surface, so whether "this is the one running" could be seen at all depended
   on which activity it happened to be. The accent is one colour, clears 3:1
   everywhere, and already means "current" in the tab bar. The category has not
   gone anywhere — it is the bar along the bottom edge of the same button. */
.activity[aria-current='true'] {
  border-color: var(--accent);
  background: var(--accent-subtle);
}

.activity__icon {
  --icon-size: 1.5rem;
  width: 2.5rem;
  height: 2.5rem;
  /* Twice the tile's own wash, so the chip separates from it. Both are mixes of
     the same category into the same surface, which keeps the pair reading as
     one object in one colour rather than as a badge sitting on a card. */
  background: color-mix(in srgb, var(--cat, transparent) 16%, var(--surface));
}

.activity__name {
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
}

/* The category as a bar along the bottom edge. Decorative — the category is
   also written out on the Activities screen and in every total. */
.activity__category {
  position: absolute;
  left: var(--space-4);
  right: var(--space-4);
  bottom: var(--space-3);
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--cat, var(--border));
}

/* ── the day strip ─────────────────────────────────────────────────── */

.strip {
  display: flex;
  width: 100%;
  height: 2.5rem;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface-sunken);
}

/* The colour arrives as an inline `--cat`, resolved from the category's own
   stored colour. The stylesheet deliberately does not know the category ids:
   they are data, and a person who adds a category must get a coloured timeline
   without anybody editing this file. */
.strip__seg {
  background: var(--cat, var(--border));
  min-width: 0;
}

/* Unnamed time is drawn as a hatch rather than a colour, so it reads as absent
   rather than as one more category — and so it is still distinguishable in
   greyscale or with any form of colour blindness. */
.strip__seg--unclassified {
  background: repeating-linear-gradient(
    135deg,
    var(--surface-sunken),
    var(--surface-sunken) 4px,
    var(--border) 4px,
    var(--border) 8px
  );
}

/* The running segment carries a bright edge as well as its colour. */
.strip__seg--running {
  box-shadow: inset -3px 0 0 0 var(--accent);
}

.strip-scale {
  display: flex;
  justify-content: space-between;
  margin-top: var(--space-2);
  color: var(--text-tertiary);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

.summary {
  display: flex;
  gap: var(--space-5);
  margin-top: var(--space-4);
}

.summary__pair dt {
  color: var(--text-tertiary);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.summary__pair dd {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

/* ── day and week navigation ───────────────────────────────────────── */

.daynav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

/* ── timeline entries ──────────────────────────────────────────────── */

.entries {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Each entry opens the editor, so it is a control and not a paragraph. The 4px
   category bar looks like enough of an edge and is not: six of the eleven
   category colours fall under 3:1 on a light surface, so on a day of meals and
   breaks the rows had no findable boundary at all. The bar keeps its hue — it
   is the category, and the category is written out beside it either way. */
.entry {
  background: var(--surface);
  border: 1px solid var(--border-control);
  border-left: 4px solid var(--cat, var(--border-strong));
  border-radius: var(--radius-sm);
}

.entry--gap {
  border-left-style: dashed;
  border-left-color: var(--border-strong);
}

.entry__button {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3);
  text-align: left;
}

.entry__icon {
  --icon-size: 1.25rem;
  width: 2rem;
  height: 2rem;
}

.entry__text {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

.entry__name {
  font-weight: var(--weight-medium);
}

.entry__times {
  color: var(--text-tertiary);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.entry__duration {
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── totals ────────────────────────────────────────────────────────── */

.totals {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.total {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-1) var(--space-3);
  align-items: baseline;
}

.total__name {
  font-weight: var(--weight-medium);
}

.total__value {
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.total__bar {
  grid-column: 1 / -1;
  display: block;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  overflow: hidden;
}

.total__fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--cat, var(--accent));
}

/* ── figures ───────────────────────────────────────────────────────── */

.figure {
  padding: var(--space-3) 0;
}

.figure__value {
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  line-height: var(--leading-tight);
}

.figure__label {
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

.figure__note {
  margin-top: var(--space-1);
  color: var(--text-tertiary);
  font-size: var(--text-xs);
}

/* ── insights ──────────────────────────────────────────────────────── */

.insights {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.insight {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border);
}

.insight__mark {
  line-height: var(--leading-normal);
}

/* Tones mark the kind of thing being said. Note the absence of a red: nothing
   here is an error, and an observation about somebody's Tuesday should never
   look like one. */
.insight--good {
  background: var(--note-bg);
  border-color: transparent;
}

.insight--good .insight__mark {
  color: var(--note);
}

.insight--attention {
  background: var(--attention-bg);
  border-color: transparent;
}

.insight--attention .insight__mark {
  color: var(--attention);
}

.insight--neutral .insight__mark {
  color: var(--text-tertiary);
}

.suggestion {
  margin-top: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--accent-subtle);
  color: var(--text);
}

/* ── the week ──────────────────────────────────────────────────────── */

.week {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.week__day {
  display: grid;
  grid-template-columns: 3rem 1fr 4rem;
  gap: var(--space-3);
  align-items: center;
  min-height: var(--tap-min);
  padding: var(--space-2);
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
}

.week__day:hover {
  background: var(--surface);
}

.week__label {
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

.week__strip {
  display: block;
  min-width: 0;
}

/* The small multiples are shorter than the full-size strip on Today, but they
   are the same component: same segments, same arithmetic, same gaps. */
.week__strip .strip {
  height: 1.25rem;
}

.week__blank {
  display: block;
  height: 1.25rem;
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
}

.week__total {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ── disclosure and workings ───────────────────────────────────────── */

.disclosure {
  margin-top: var(--space-3);
}

.disclosure summary {
  cursor: pointer;
  color: var(--accent);
  font-size: var(--text-sm);
  min-height: var(--tap-min);
  display: flex;
  align-items: center;
}

.working {
  margin-top: var(--space-3);
}

.working__note {
  color: var(--text-tertiary);
  font-size: var(--text-sm);
}

.working__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}

.working__list li {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  font-size: var(--text-sm);
}

.working__figures {
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ── menus and list rows ───────────────────────────────────────────── */

.menu {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.menu__link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-comfortable);
  padding: var(--space-4);
  background: var(--surface);
  /* These three rows are the whole of the More screen, and until this line they
     were white cards on a white page edged at 1.19:1 — a menu whose items you
     had to already know were there. */
  border: 1px solid var(--border-control);
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
}

.menu__link:hover {
  border-color: var(--text-tertiary);
}

.menu__mark {
  font-size: var(--text-lg);
  color: var(--text-tertiary);
  line-height: 1;
}

.menu__text {
  display: flex;
  flex-direction: column;
}

.menu__label {
  font-weight: var(--weight-medium);
}

.menu__hint {
  color: var(--text-tertiary);
  font-size: var(--text-sm);
}

.rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.row-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-right: var(--space-2);
  background: var(--surface);
  /* Same reasoning as `.entry`: this row holds three controls — the activity
     itself and the two reorder arrows — and the card edge is what says where
     one row's targets stop and the next row's begin. */
  border: 1px solid var(--border-control);
  border-left: 4px solid var(--cat, var(--border-control));
  border-radius: var(--radius-sm);
}

.row-item--muted {
  opacity: 0.7;
}

.row-item__main {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1;
  min-width: 0;
  min-height: var(--tap-comfortable);
  padding: var(--space-3);
  text-align: left;
}

.row-item__main[aria-pressed='true'] {
  background: var(--accent-subtle);
}

.row-item__icon {
  --icon-size: 1.25rem;
  width: 2.125rem;
  height: 2.125rem;
}

.row-item__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.row-item__name {
  font-weight: var(--weight-medium);
}

.row-item__hint {
  color: var(--text-tertiary);
  font-size: var(--text-sm);
}

.row-item__controls {
  display: flex;
  gap: var(--space-1);
}

.icon-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
}

.icon-btn:hover:not(:disabled) {
  background: var(--surface-sunken);
}

.icon-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

/* ── forms ─────────────────────────────────────────────────────────── */

.field + .field {
  margin-top: var(--space-4);
}

.field__label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
}

.field__hint {
  margin-top: var(--space-2);
  color: var(--text-tertiary);
  font-size: var(--text-sm);
}

.input,
.select {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3);
  font: inherit;
  color: var(--text);
  background: var(--surface);
  /* The fill is the same white as the card behind it, so this outline is the
     only thing that says "type here". It is a control border for that reason
     and not for emphasis. */
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
}

.select {
  /* The platform's own arrow is left in place: replacing it costs the native
     picker on mobile, which is better than anything built here. */
  appearance: auto;
}

.switch {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
  min-height: var(--tap-comfortable);
  padding: var(--space-3) 0;
  text-align: left;
}

.switch + .switch {
  border-top: 1px solid var(--border);
}

.switch__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.switch__label {
  font-weight: var(--weight-medium);
}

.switch__hint {
  color: var(--text-tertiary);
  font-size: var(--text-sm);
}

.switch__track {
  flex: none;
  position: relative;
  width: 3rem;
  height: 1.75rem;
  border-radius: var(--radius-pill);
  /* The off state. On is `--accent` and easy; off is the one that gets drawn as
     a pale grey nobody can find, and it has to clear 3:1 twice over — against
     the row behind it, and against the knob sitting on it, which is what says
     which way the switch is thrown. */
  background: var(--border-control);
  transition: background var(--motion-fast) var(--ease);
}

.switch__knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--surface);
  transition: transform var(--motion-fast) var(--ease);
}

.switch[aria-checked='true'] .switch__track {
  background: var(--accent);
}

.switch[aria-checked='true'] .switch__knob {
  transform: translateX(1.25rem);
}

/* The state is in aria-checked and is announced as on or off. This adds a
   second visible cue beyond the knob's position, for anybody who reads the
   control as a shape rather than as a slider. */
.switch[aria-checked='true'] .switch__track::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 0.6rem;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent-text);
  transform: translateY(-50%);
}

/* ── buttons ───────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  font-weight: var(--weight-medium);
  color: var(--text);
  background: var(--surface);
  /* A default button is white on white inside a card, so — as with the input —
     the outline is the button. `--btn--primary` fills with the accent and
     `--btn--quiet` deliberately has no edge at all, and neither needs this. */
  border: 1px solid var(--border-control);
  border-radius: var(--radius-sm);
  transition:
    background var(--motion-fast) var(--ease),
    border-color var(--motion-fast) var(--ease);
}

.btn:hover:not(:disabled) {
  border-color: var(--text-tertiary);
}

.btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.btn--primary {
  color: var(--accent-text);
  background: var(--accent);
  border-color: var(--accent);
}

.btn--quiet {
  background: none;
  border-color: transparent;
  color: var(--text-secondary);
}

.btn--quiet:hover:not(:disabled) {
  background: var(--surface);
  border-color: var(--border);
}

.btn--block {
  display: flex;
  width: 100%;
  margin-top: var(--space-3);
}

/* ── empty states ──────────────────────────────────────────────────── */

.empty {
  padding: var(--space-6) var(--space-4);
  text-align: center;
  background: var(--surface);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
}

.empty__title {
  font-weight: var(--weight-semibold);
}

.empty__body {
  margin-top: var(--space-2);
  color: var(--text-secondary);
}

.empty .btn {
  margin-top: var(--space-4);
}

/* ── sheets ────────────────────────────────────────────────────────── */

.sheet {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgb(0 0 0 / 45%);
  z-index: 50;
}

@media (min-width: 40rem) {
  .sheet {
    align-items: center;
  }
}

.sheet__panel {
  width: 100%;
  max-width: 32rem;
  max-height: 90dvh;
  overflow-y: auto;
  background: var(--surface);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-raised);
  padding-bottom: env(safe-area-inset-bottom, 0);
}

@media (min-width: 40rem) {
  .sheet__panel {
    border-radius: var(--radius-lg);
  }
}

.sheet__head {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.sheet__title {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
}

.sheet__close {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--tap-min);
  height: var(--tap-min);
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
}

.sheet__body {
  padding: var(--space-4);
}

.sheet__lead {
  margin-bottom: var(--space-4);
  color: var(--text-secondary);
}

/* Scrolling the page behind an open sheet is disorienting, and on iOS it
   scrolls the page rather than the panel. */
body.is-sheet-open {
  overflow: hidden;
}

/* ── edit plans ────────────────────────────────────────────────────── */

.plan:empty {
  display: none;
}

.plan {
  margin-top: var(--space-4);
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  font-size: var(--text-sm);
}

.plan__title {
  font-weight: var(--weight-medium);
  margin-bottom: var(--space-2);
}

.plan__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  color: var(--text-secondary);
}

.plan__list li::before {
  content: '· ';
  color: var(--text-tertiary);
}

.plan__list--problem {
  color: var(--attention);
}

/* ── toasts ────────────────────────────────────────────────────────── */

#toasts {
  position: fixed;
  left: 50%;
  bottom: calc(var(--tap-comfortable) + var(--space-3) + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  width: calc(100% - var(--space-6));
  max-width: 26rem;
  z-index: 40;
  /* The container never intercepts taps; only the bar inside it does. Without
     this, an invisible strip across the screen swallows presses on the
     activity buttons underneath. */
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-raised);
  pointer-events: auto;
}

.toast--error {
  border-color: var(--attention);
  background: var(--attention-bg);
}

.toast__text {
  min-width: 0;
}

.toast__action {
  flex: none;
  min-height: var(--tap-min);
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  color: var(--accent);
  font-weight: var(--weight-medium);
}

.toast__action:hover {
  background: var(--accent-subtle);
}
