/*
 * The one shared stylesheet. Every colour, space, size, radius and shadow used
 * anywhere in the app is a custom property defined here, so screen one and
 * screen seven cannot drift apart.
 *
 * The values are the ones decided in 004, against the real fixture stash rather
 * than against lorem ipsum — direction A, "paper and ink", in
 * `specs/004/design-brief.md` and drawn out on the design canvas in
 * `design/004/`. The accent moved from brown to deep petrol because half the
 * stash is beige, mustard and cognac, and a brown accent on warm paper is
 * precisely the feeling she called old-fashioned.
 */

:root {
  /* Colour */
  --colour-page: #faf8f5;
  --colour-surface: #ffffff;
  --colour-slot: #f1ece5;
  --colour-border: #e9e3db;
  --colour-border-soft: #f1ece5;
  --colour-text: #1b1917;
  --colour-text-quiet: #6f6862;
  --colour-text-faint: #b0a7a0;
  --colour-accent: #125c5f;
  --colour-accent-text: #ffffff;
  --colour-accent-soft: #e4efee;
  /* The hairline inside the soft panel on `onboarding.html`, and the one
     token 010 added. `--colour-border` is warm, and a warm rule on a
     petrol-tinted ground reads as a mistake rather than as a rule. */
  --colour-accent-border: #cfe2e0;
  --colour-reserved: #7a4a12;
  --colour-reserved-surface: #f7eeda;
  --colour-reserved-border: #e3cda1;
  --colour-error: #8f2f28;
  --colour-error-surface: #fbeeec;
  --colour-error-text: #5c3a35;
  --colour-error-border: #e3b9b3;
  /* Ink at 62%, for the one place something is laid over a photograph. A
     picture is the only ground in this app that is not one of the colours
     above, so it is the only thing needing a token that is not opaque. */
  --colour-scrim: rgb(27 25 23 / 62%);
  --colour-scrim: rgb(27 25 23 / 32%);

  /* Spacing — a single scale, so gaps line up across screens */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;

  /* Type */
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --text-micro: 0.6875rem;
  --text-small: 0.8125rem;
  --text-meta: 0.9375rem;
  --text-body: 1rem;
  --text-large: 1.0625rem;
  --text-lot: 1.1875rem;
  --text-title: 1.25rem;
  --text-display: 1.625rem;
  --line-tight: 1.25;
  --line-body: 1.5;

  /* Radius and shadow */
  --radius-small: 6px;
  --radius-medium: 10px;
  --radius-large: 14px;
  --radius-pill: 999px;
  --shadow-low: 0 1px 2px rgb(27 25 23 / 6%);
  --shadow-raised: 0 8px 24px rgb(27 25 23 / 14%);

  /* Layout — the desktop layout is a constrained column, never a stretched
     phone screen */
  --measure-page: 60rem;
  --tap-target: 44px;
  --header-height: 3.5rem;
  --action-height: 3.25rem;
  --photo-slot: 2.75rem;
  --capture-tile: 6rem;
  --capture-remove: 2.5rem;
  --breakpoint-wide: 48rem;
}

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

body {
  margin: 0;
  padding: var(--space-5) var(--space-4) var(--space-7);
  background: var(--colour-page);
  color: var(--colour-text);
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: var(--line-body);
}

main {
  max-width: var(--measure-page);
  margin: 0 auto;
}

h1 {
  margin: 0 0 var(--space-2);
  font-size: var(--text-display);
  line-height: var(--line-tight);
}

h2 {
  margin: 0 0 var(--space-3);
  font-size: var(--text-title);
  line-height: var(--line-tight);
}

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

/* Focus is visible on everything that takes it, in the accent, against the
   paper. Keyboard-only use is a requirement, not a courtesy. */

a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: 2px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.button {
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-5);
  border: none;
  border-radius: var(--radius-medium);
  background: var(--colour-accent);
  color: var(--colour-accent-text);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

/* The four states */

.state {
  padding: var(--space-4);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-large);
  background: var(--colour-surface);
  box-shadow: var(--shadow-low);
}

.state__label,
.state__message {
  display: block;
  margin: 0 0 var(--space-3);
  color: var(--colour-text-quiet);
}

.state__title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-large);
  color: var(--colour-error);
}

.state--error {
  border-color: var(--colour-error-border);
  background: var(--colour-error-surface);
  box-shadow: none;
}

.skeleton {
  height: var(--space-4);
  margin-bottom: var(--space-2);
  border-radius: var(--radius-small);
  background: var(--colour-border);
}

.skeleton:last-child {
  width: 60%;
  margin-bottom: 0;
}

/* Counts and stash lines */

.counts,
.lines {
  margin: 0;
  padding: 0;
  list-style: none;
}

.count,
.lines li {
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--colour-border);
}

.count {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
}

.count__value {
  font-variant-numeric: tabular-nums;
}

/*
 * The app frame — every screen that wears the shared header.
 *
 * The page itself has no gutters: the header spans the width and the list
 * hairlines run edge to edge, so each block carries its own padding instead.
 */

.app {
  padding: 0;
}

.app .screen {
  max-width: var(--measure-page);
  margin: 0 auto;
  padding: 0 0 var(--space-7);
}

.app .state {
  margin: var(--space-4);
}

.app .state--empty {
  padding: var(--space-6) var(--space-4);
  text-align: center;
}

.app .state--empty .state__message {
  margin: 0;
  color: var(--colour-text);
}

/* The way out, under the answer. It is instructional rather than the finding
   itself, so it is quieter — and nothing here borrows the error surface: not
   matching a search is an answer, and often good news. */

.state__hint {
  margin: var(--space-2) 0 0;
  font-size: var(--text-small);
  color: var(--colour-text-quiet);
}

/*
 * The start-up failure sentence, and the no-JavaScript fallback — the same
 * paragraph doing both jobs.
 *
 * It is in the markup from first paint so it survives JavaScript being off, and
 * it is hidden the moment the inline mark in `<head>` says JavaScript is
 * running. The module reveals it again if start-up actually fails. Without this
 * it is on screen for the whole of a slow load, telling her the app failed
 * while the app is busy starting — which is the same lie as hiding a failure
 * that did happen.
 */

.startup-failed {
  margin: var(--space-5) var(--space-4);
  color: var(--colour-text-quiet);
}

.js-booting .startup-failed {
  display: none;
}

/* The frame and the shape of the list, from first paint, on any connection.
   Both are static markup carrying no words, and both are replaced the instant
   `t()` exists — so the screen is recognisably itself while it waits, instead of
   white. Neither survives JavaScript being off: a skeleton that never resolves
   would be a screen pretending to load. */

.header--waiting {
  min-height: var(--header-height);
}

.boot-skeleton {
  display: none;
}

.js-booting .boot-skeleton {
  display: block;
}

/* The sentence carries what went wrong; the heading above it does not. It is
   set for the pink surface rather than left in the neutral grey the loading
   label uses. */

.app .state--error .state__message {
  color: var(--colour-error-text);
}

/* The shared header, and its menu */

.header {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--colour-page);
  border-bottom: 1px solid var(--colour-border);
}

.header__bar {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  max-width: var(--measure-page);
  min-height: var(--header-height);
  margin: 0 auto;
  padding: 0 var(--space-4);
  background: var(--colour-page);
}

.header__brand {
  display: flex;
  align-items: center;
  min-height: var(--tap-target);
  font-size: var(--text-large);
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--colour-text);
  text-decoration: none;
}

.header__menu-button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-target);
  height: var(--tap-target);
  margin-right: calc(var(--space-2) * -1);
  padding: 0;
  border: none;
  border-radius: var(--radius-medium);
  background: transparent;
  color: var(--colour-text);
  cursor: pointer;
}

.header__menu-button[aria-expanded="true"] {
  background: var(--colour-accent-soft);
  color: var(--colour-accent);
}

.header__menu-button .icon--open,
.header__menu-button[aria-expanded="true"] .icon--closed {
  display: none;
}

.header__menu-button[aria-expanded="true"] .icon--open {
  display: block;
}

.header__scrim {
  position: fixed;
  inset: 0;
  z-index: 1;
  background: var(--colour-scrim);
}

.header__menu {
  position: absolute;
  top: calc(100% + var(--space-1));
  right: max(
    var(--space-3),
    calc((100% - var(--measure-page)) / 2 + var(--space-3))
  );
  z-index: 3;
  width: min(15.5rem, calc(100% - var(--space-6)));
  overflow: hidden;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-large);
  background: var(--colour-surface);
  box-shadow: var(--shadow-raised);
}

.header__items {
  margin: 0;
  padding: 0;
  list-style: none;
}

.header__items li + li .header__item {
  border-top: 1px solid var(--colour-border-soft);
}

.header__item {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-4);
  border: none;
  background: transparent;
  color: var(--colour-text);
  font: inherit;
  text-align: left;
  text-decoration: none;
}

a.header__item:hover {
  background: var(--colour-accent-soft);
}

/* Inset, because a menu item fills the panel and the panel clips its own
   overflow to keep the rounded corners. An outline sitting outside the item
   would be trimmed off on both sides, leaving a keyboard user with a focus ring
   in pieces. */

.header__item:focus-visible {
  outline-offset: -2px;
}

/* Sign-out, and the sentence it prints when it did not work.
 *
 * The sentence lives beneath the button and inside the panel, so it is where
 * she is already looking. `.banner` carries the app's gutter for the framed
 * screens; here the panel is the gutter, so the margin goes and a little air
 * above it takes its place. */

.signout__message:empty {
  display: none;
}

.signout__banner {
  margin: 0 var(--space-3) var(--space-3);
}

/* The way out at the foot of `onboarding.html` - the one screen with no header.
   Quiet on purpose: the whole case for that screen is that there is exactly one
   thing to do on it, and this is not it. */

.onboarding__sign-out {
  min-height: var(--tap-target);
  padding: var(--space-2) 0;
  border: none;
  background: transparent;
  color: var(--colour-text-quiet);
  font: inherit;
  font-size: var(--text-meta);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}

.onboarding__way-out {
  margin-top: var(--space-6);
  text-align: center;
}

.onboarding__way-out .signout__banner {
  margin-inline: 0;
  text-align: left;
}

/* The one action, and the summary under it */

.action {
  padding: var(--space-3) var(--space-4) var(--space-2);
}

.action__capture {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--action-height);
  border-radius: var(--radius-medium);
  background: var(--colour-accent);
  color: var(--colour-accent-text);
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.summary {
  padding: var(--space-1) var(--space-4) var(--space-3);
}

/* Absent while loading, rather than an empty band of padding where three
   numbers are about to appear. */

.summary:empty {
  display: none;
}

.summary__items {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-small);
  color: var(--colour-text-quiet);
}

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

.summary__item + .summary__item::before {
  content: "·";
  color: var(--colour-text-faint);
}

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

.summary__note {
  margin: var(--space-1) 0 0;
  font-size: var(--text-small);
  color: var(--colour-text-faint);
}

/*
 * The filter bar.
 *
 * Not sticky, and the header is: two sticky bars on a phone eat the screen the
 * list needs, and the escape hatch — the app name — is in the sticky one, which
 * is the part that must never require scrolling.
 *
 * Native controls throughout. A `<select>` on a phone opens the platform
 * picker, which is big-thumbed, scrollable, accessible and free; a hand-built
 * dropdown is a focus, keyboard and screen-reader problem this project has no
 * framework to hide. Nothing here transitions or animates: every filter is
 * instant, and anything that implies waiting would be a lie about that.
 */

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

/* Absent while loading, and in every state that has no rows to narrow. */

.filter:empty {
  display: none;
}

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

/* Wrapping, never shrinking and never scrolling sideways: at 320 px the
   controls drop onto their own lines and keep their tap targets. */

.filter-bar__controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.filter-bar__search,
.filter-bar__control {
  margin: 0;
}

.filter-bar__control {
  flex: 1 1 9rem;
  min-width: 0;
}

.filter-bar__input,
.filter-bar__select {
  width: 100%;
  min-height: var(--tap-target);
  padding: 0 var(--space-3);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-medium);
  background: var(--colour-surface);
  color: var(--colour-text);
  font: inherit;
}

.filter-bar__input::placeholder {
  color: var(--colour-text-faint);
}

/* Three states, one control, one tab stop. The fieldset needs its own
   min-width: a fieldset's default min-inline-size is min-content, which would
   refuse to wrap and push the bar sideways. */

.filter-bar__availability {
  display: flex;
  flex: 1 1 13rem;
  min-width: 0;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-medium);
  background: var(--colour-surface);
}

.filter-bar__segment {
  position: relative;
  display: flex;
  flex: 1 1 0;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-target);
  padding: 0 var(--space-2);
  font-size: var(--text-small);
  color: var(--colour-text-quiet);
  text-align: center;
  cursor: pointer;
}

.filter-bar__segment + .filter-bar__segment {
  border-left: 1px solid var(--colour-border);
}

/* The radio is not drawn; the segment it labels is. It stays in the document
   rather than being display:none, because that would take it out of the tab
   order and out of the arrow-key group that makes the three one stop. */

.filter-bar__radio {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  opacity: 0;
}

/* Weight as well as ground, so the chosen state is not carried by colour. */

.filter-bar__segment:has(.filter-bar__radio:checked) {
  background: var(--colour-accent-soft);
  color: var(--colour-text);
  font-weight: 600;
}

.filter-bar__segment:has(.filter-bar__radio:focus-visible) {
  outline: 2px solid var(--colour-accent);
  outline-offset: -2px;
}

/* The list, and the row */

.stash-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.stash-row {
  border-top: 1px solid var(--colour-border-soft);
}

.stash-row__link {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  color: inherit;
  text-decoration: none;
}

.stash-row__link:hover {
  background: var(--colour-surface);
}

.stash-row__link:focus-visible {
  outline-offset: -2px;
}

.stash-row__slot {
  flex: none;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  width: var(--photo-slot);
}

/* Empty until 023, and reading as "no photograph yet" rather than as broken.
   The colour's photograph belongs to the colour, so a repeated colour gets a
   tie up to the row above rather than the same square again. */

.stash-row__photo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--photo-slot);
  height: var(--photo-slot);
  border-radius: var(--radius-medium);
  background: var(--colour-slot);
  color: var(--colour-text-faint);
}

.stash-row--repeat .stash-row__photo {
  display: none;
}

.stash-row--repeat .stash-row__slot::after {
  content: "";
  width: 1px;
  height: var(--photo-slot);
  background: var(--colour-border);
}

.stash-row__body {
  flex: 1;
  min-width: 0;
}

.stash-row__identity {
  display: block;
  font-size: var(--text-small);
  line-height: var(--line-tight);
  color: var(--colour-text-quiet);
  overflow-wrap: break-word;
}

/*
 * The first row of a colour carries its name, and carries it in bold.
 *
 * Her verdict on the built list, overturning 004: scrolling a hundred lots is
 * looking for a colour, not for a number, so the row that introduces one has to
 * be findable at speed. It is the row that already has the photograph slot, so
 * the weight and the thumbnail mark the same thing — and the repeats below it
 * stay faint, which is what keeps the group reading as a group.
 *
 * It steps aside for a finished row rather than being overridden below: this
 * selector outweighs the used-up rules, so a lot she has finished would come
 * back louder than the ones she still owns.
 */

.stash-row:not(.stash-row--repeat):not(.stash-row--finished) .stash-row__identity {
  font-size: var(--text-body);
  font-weight: 600;
  color: var(--colour-text);
}

.stash-row--repeat .stash-row__identity {
  color: var(--colour-text-faint);
}

.stash-row__main {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0 var(--space-3);
}

/* Plain text, and the same size as the quantity beside it, by her decision. 004
   set the lot as the loudest thing on the row; she overturned that on the real
   list, because a number she reads once she has already found the colour does
   not need to be the thing she scans for. The two values on this line are now
   one line rather than a headline and a footnote. Tabular figures stay — that
   is columns lining up, not emphasis. */

.stash-row__lot {
  font-size: var(--text-meta);
  font-variant-numeric: tabular-nums;
}

.stash-row__quantity {
  font-size: var(--text-meta);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.stash-row__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.stash-row__badge {
  padding: 0 var(--space-2);
  border: 1px solid var(--colour-reserved-border);
  border-radius: var(--radius-pill);
  background: var(--colour-reserved-surface);
  color: var(--colour-reserved);
  font-size: var(--text-small);
  font-weight: 600;
}

.stash-row__note {
  font-size: var(--text-small);
  color: var(--colour-reserved);
}

/*
 * The three exceptions, marked three ways, because they are not the same kind
 * of thing.
 *
 * A state she acts on gets a rule in the margin, where the eye picks up a
 * rhythm down a long list without any single row shouting. Two rows in five
 * carry some exception, so anything louder than this turns the list into an
 * error log — which is the feeling she rejected before she rejected Ravelry.
 */

.stash-row--reserved {
  box-shadow: inset 3px 0 0 var(--colour-reserved);
}

/* Not measured yet, which is a gap rather than an alarm — and the commonest
   thing on the screen while she is entering five hundred skeins. It reads as
   unfinished, never as a number. */

.stash-row--unweighed .stash-row__quantity {
  color: var(--colour-text-quiet);
  font-style: italic;
}

/* The label printed no name. Marked as a placeholder so it cannot be read as a
   brand actually called "uden mærke"; corrected on the yarn screen in 026. */

.stash-row--unnamed .stash-row__identity {
  font-style: italic;
}

/* Used up is kept, not deleted: the whole row recedes and the word stands in
   the quantity. No red, no strike-through, no warning sign — it is finished,
   not wrong. */

.stash-row--finished .stash-row__identity,
.stash-row--finished .stash-row__lot,
.stash-row--finished .stash-row__quantity {
  color: var(--colour-text-faint);
}

.stash-row--finished .stash-row__photo {
  background: var(--colour-page);
  color: var(--colour-border);
}

/* --- 006: add.html, the by-hand form ------------------------------------- */

/* A task screen names itself. The stash list is the home screen and the app
   name in the header names that one; this is the only h1 in the app. */

.page-title {
  margin: 0;
  padding: var(--space-4) var(--space-4) var(--space-2);
  font-size: var(--text-title);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.lede {
  margin: 0 0 var(--space-4);
  color: var(--colour-text-quiet);
  font-size: var(--text-meta);
  line-height: var(--line-body);
}

/* Persistent, never a toast: a message about her data must not vanish while
   she is looking at yarn rather than at the phone. */

.banner {
  margin: 0 var(--space-4) var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--colour-error-border);
  border-radius: var(--radius-medium);
  background: var(--colour-error-surface);
  color: var(--colour-error-text);
  font-size: var(--text-meta);
  line-height: var(--line-body);
}

/* A real `<form>`, for the two things iOS gives one and gives nothing else: the
   `< >` bar above the keyboard, which is the only next-field control that works
   above the numeric keypad, and a return key that reports itself even with
   predictive text on. See `add.html`. */

/* One gutter, on the screen rather than on each block inside it, exactly as
   `.lot` does for 007. Required rather than tidy-minded since the cards went:
   the fields have no padding of their own any more, so without this every label
   and box would sit flush against the edge of the phone. */

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

.add-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin: 0;
}

/* A fieldset per level of the model, so a screen reader says which of the four
   a control belongs to — Mærke and Garn both hold a field called a name.

   **No card, and no border.** Both forms in this app were drawn as bordered,
   shadowed cards with upper-case grey legends above them, and she overturned it
   three times before it landed: the legend was a caption for a group while doing
   a label's job, so it read as both and as neither, and at that size and case it
   read as faint besides. The `fieldset` and `legend` stay — they are what a
   screen reader uses to keep *Navn på mærket* and *Garnets navn* apart — and
   nothing draws them as furniture. */

.section {
  margin: 0;
  padding: 0;
  border: none;
  background: none;
}

/* Still waiting on the section above it: the greyed name is the whole of "not
   yet your turn", and it is how she sees how much is left to answer. With no
   card to drop, this is all that is left to say it. */

.section--waiting .section__legend {
  color: var(--colour-text-faint);
}

/* Set exactly like a field's own label — same size, same case, same colour, the
   same gap above the control that *Navn på mærket* has above its box. It is
   drawn only where it labels the picker beneath it or stands in for a section
   not yet her turn; everywhere else the markup hides it, because a `legend` names
   a group and the fields below already carry their own labels.

   Deliberately sets no `display` and no `width`: a `<legend>` is already a block
   by default, and claiming either here would outrank `.visually-hidden`. */

.section__legend {
  padding: 0;
  margin: 0 0 var(--space-1);
  color: var(--colour-text-quiet);
  font-size: var(--text-small);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

.section__body:empty {
  display: none;
}

.section__picker .field {
  margin-bottom: 0;
}

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

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

.field:last-child {
  margin-bottom: 0;
}

/* Visible, never placeholder-only: a placeholder vanishes the moment she types,
   which is exactly when "what is this field?" becomes a live question — and she
   has to be able to read her answers back before saving. */

.field__label {
  color: var(--colour-text-quiet);
  font-size: var(--text-small);
}

.field__input,
.field__select {
  width: 100%;
  min-height: var(--tap-target);
  padding: 0 var(--space-3);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-medium);
  background: var(--colour-page);
  color: var(--colour-text);
  font: inherit;
}

.field__input:focus-visible,
.field__select:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: 2px;
}

/* The number and its unit on one line: "50" alone is not a weight, and a unit
   that wraps below the box at 320 px reads as a stray letter. */

.field--measure {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "label label" "input unit";
  align-items: center;
  column-gap: var(--space-2);
}

.field--measure .field__label {
  grid-area: label;
}

.field--measure .field__input {
  grid-area: input;
}

.field__unit {
  grid-area: unit;
  color: var(--colour-text-quiet);
  font-size: var(--text-meta);
}

/* An error is a sentence beside the field it is about, and colour is never the
   whole message. */

.field__error {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--colour-error);
  font-size: var(--text-small);
  line-height: var(--line-body);
}

/* A warning, and deliberately not --colour-error: the impossible weight is
   saved anyway, and a refusal-coloured sentence beside a value that will be
   kept is the screen saying something it does not mean. */

.field__note {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--colour-text-quiet);
  font-size: var(--text-small);
  line-height: var(--line-body);
}

/* Read-only, because correcting a fibre corrects it for every colour of that
   yarn — that is 026's screen, not the one she uses forty times a week. */

.facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-1) var(--space-4);
  margin: 0 0 var(--space-3);
}

.facts__term {
  color: var(--colour-text-quiet);
  font-size: var(--text-small);
}

.facts__value {
  margin: 0;
  font-size: var(--text-meta);
}

.facts__value--absent {
  color: var(--colour-text-faint);
}

.facts__link {
  color: var(--colour-accent);
  font-size: var(--text-small);
}

.button {
  min-height: var(--tap-target);
  padding: 0 var(--space-5);
  border: 1px solid transparent;
  border-radius: var(--radius-medium);
  font: inherit;
  cursor: pointer;
}

.button:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: 2px;
}

.button--primary {
  width: 100%;
  min-height: var(--action-height);
  margin-top: var(--space-5);
  background: var(--colour-accent);
  color: var(--colour-accent-text);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.button--primary:disabled {
  background: var(--colour-text-faint);
  cursor: default;
}

.button--quiet {
  border-color: var(--colour-border);
  background: var(--colour-surface);
  color: var(--colour-text);
  font-size: var(--text-meta);
}

/* The only confirmation a save gets, so it persists until the next one
   replaces it. */

.saved {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  margin: var(--space-4) 0 0;
}

.saved:empty {
  margin: 0;
}

.saved__label {
  color: var(--colour-accent);
  font-size: var(--text-small);
  font-weight: 600;
}

.saved__line {
  font-size: var(--text-meta);
}

.saved__link {
  color: var(--colour-accent);
  font-size: var(--text-small);
}

/* --- 007: lot.html, one lot and what she can do to it -------------------- */

/* One gutter, on the screen rather than on each block inside it.
   `.app .screen` has none of its own below the breakpoint, and 004 gives each
   block its own padding because the list's hairlines run edge to edge. Nothing
   here does: **Gem** is full width, its confirmation is a panel, and the saved
   line is a sentence — all three are children of this element rather than of the
   form, so a per-block inset would have left them touching both edges while the
   cards above them were inset, and put the save's confirmation on a different
   left edge from the two below it. */

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

/* The identity block: the photograph slot 004 established, the lot's identity,
   its number, and the yarn's facts as read-only text. */

.lot-identity {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4) 0 var(--space-2);
}

.lot-identity__slot {
  flex: none;
}

/* Empty until 023, and reading as "no photograph yet" rather than as broken —
   004's slot, at 004's size, so the two screens agree about what a colour looks
   like before it has been photographed. */

.lot-identity__photo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--photo-slot);
  height: var(--photo-slot);
  border-radius: var(--radius-medium);
  background: var(--colour-slot);
  color: var(--colour-text-faint);
}

.lot-identity__body {
  flex: 1;
  min-width: 0;
}

/* Not the name of a task — this screen is *about* a dye lot of a colour of a
   yarn of a brand. Smaller than 006's page title, because the identity is three
   names joined and a display size turns it into two lines on a phone. */

.lot-identity__title {
  margin: 0;
  font-size: var(--text-title);
  font-weight: 600;
  line-height: var(--line-tight);
  letter-spacing: -0.01em;
  overflow-wrap: break-word;
}

/* The one reader `--text-lot` was left in the scale for. 004's lesson is about
   travelling through a hundred rows; here she has arrived, there are no rows to
   group, and nothing else says which of Mushroom's three lots this is. */

.lot-identity__lot {
  margin: var(--space-1) 0 0;
  font-size: var(--text-lot);
  font-variant-numeric: tabular-nums;
  line-height: var(--line-tight);
}

.lot-identity__body .facts {
  margin: var(--space-3) 0 0;
}

.lot-identity__links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  margin-top: var(--space-2);
}

/* Used up is kept, not deleted. 004's treatment one screen along: the words
   carry it and no colour does — nothing here is red, nothing is struck through,
   and the fields below stay editable, which is what "kept" means in practice. */

.lot-note {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-medium);
  background: var(--colour-slot);
}

.lot-note__text {
  margin: 0;
  color: var(--colour-text-quiet);
  font-size: var(--text-meta);
  line-height: var(--line-body);
}

.lot-note__quantity {
  margin: var(--space-1) 0 0;
  font-size: var(--text-meta);
  font-variant-numeric: tabular-nums;
}

/* A real `<form>`, for the two things iOS gives one and gives nothing else —
   see `lib/ui/lotForm.js`. One column at every width: a two-column form asks
   her to read in a Z, and this one is answered downward. */

.lot-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin: 0;
}

/* The finished-and-gone area: the two things typing again cannot undo, last on
   the screen and last in the tab order, so she reaches everything she came for
   before she reaches anything irreversible. */

.lot-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: var(--space-6) 0 0;
  padding-top: var(--space-5);
  border-top: 1px solid var(--colour-border);
}

.lot-confirm-host:empty {
  display: none;
}

/* In place, two buttons, no modal. 004 rejected a focus trap for a three-item
   menu and this is two buttons and a sentence; native `confirm()` cannot be
   translated or styled, which would make the most serious sentence in the app
   the only one not in Danish. */

.confirm {
  padding: var(--space-4);
  border: 1px solid var(--colour-border);
  border-radius: var(--radius-medium);
  background: var(--colour-surface);
  box-shadow: var(--shadow-low);
}

.confirm__sentence {
  margin: 0 0 var(--space-2);
  font-size: var(--text-meta);
  line-height: var(--line-body);
}

/* What she is about to destroy, in the words 004's row uses. */

.confirm__line {
  margin: 0 0 var(--space-3);
  color: var(--colour-text-quiet);
  font-size: var(--text-small);
  line-height: var(--line-body);
  overflow-wrap: break-word;
}

.confirm__buttons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.confirm__buttons .button {
  flex: 1 1 8rem;
}

/* The accent, for a confirmation that destroys nothing: marking a lot used up is
   a state she acts on, and a save is a save. */

.button--accent {
  background: var(--colour-accent);
  color: var(--colour-accent-text);
  font-weight: 600;
}

/* Reinforcement, never the message: the button is named with the verb, and the
   sentence above it says what happens. */

.button--danger {
  border-color: var(--colour-error-border);
  background: var(--colour-error-surface);
  color: var(--colour-error);
  font-weight: 600;
}

.button--block {
  width: 100%;
}

.button:disabled {
  cursor: default;
  opacity: 0.6;
}

/* The way out of the not-found panel, which is the only place on this screen
   where a visible way back to the list is drawn. */

.state__link {
  color: var(--colour-accent);
  font-size: var(--text-meta);
}

/* `hidden` has to win. The lot number line and the used-up note are shown and
   hidden by the attribute, and any author `display` on their own class would
   quietly outrank the browser's rule for it. */

[hidden] {
  display: none !important;
}

/* The one breakpoint: a real desktop layout, not a phone column stretched
   across a monitor. The row becomes columns and the numbers line up down the
   page. */

/* --- 008: the capture block, the match panel and the disagreement -------- */

/* The fifth section of the form, wearing the same card as the other four so a
   screen reader and an eye both place it the same way. */

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

.capture__input {
  display: none;
}

/* Picture, picture, add — one row read left to right, with no control standing
   outside it. Her decision. Three tiles and their gaps fit at 320 px with room
   to spare: they shrink before they wrap, and they never go below a tap
   target. */

.capture__strip {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.capture__tile {
  position: relative;
  flex: 0 1 var(--capture-tile);
  min-width: var(--tap-target);
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-medium);
  background: var(--colour-slot);
  overflow: hidden;
}

.capture__thumb {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A bar across the foot of the picture rather than a dot in its corner. It is
   the one control in the app narrower than `--tap-target` in one dimension, and
   it is wider in the other: the area is larger than a 44 px square, the thumb
   lands on it without covering the picture she is checking, and a corner dot
   this size would have been the worse trade in both directions. */

.capture__remove {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: var(--capture-remove);
  border: none;
  background: var(--colour-scrim);
  color: var(--colour-accent-text);
  font: inherit;
  font-size: var(--text-large);
  line-height: 1;
  cursor: pointer;
}

.capture__remove::before {
  content: "×";
}

.capture__remove:focus-visible {
  outline: 2px solid var(--colour-accent-text);
  outline-offset: -3px;
}

/* An empty photo slot with a dashed edge, so the row reads as *picture,
   picture, add* rather than as *picture, picture, something else*. The glyph is
   drawn here and the name is translated in the markup: a bare `＋` is a label
   she can see and not one a screen reader can use. */

.capture__tile--add {
  background: none;
}

.capture__add {
  width: 100%;
  height: 100%;
  border: 1px dashed var(--colour-border);
  border-radius: var(--radius-medium);
  background: var(--colour-slot);
  color: var(--colour-text-quiet);
  font: inherit;
  font-size: var(--text-display);
  line-height: 1;
  cursor: pointer;
}

.capture__add::before {
  content: "＋";
}

.capture__add:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: 2px;
}

/* The read button is full width in the same shape as **Gem**: it is the primary
   action of this block and what her thumb goes to after the last picture.
   Annullér is on its own line beneath it, never beside it — putting the control
   that undoes the read where the read was is how a slow network becomes a
   cancelled read she did not ask for. */

.capture__controls {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.capture__controls:empty {
  display: none;
}

.capture__controls .button--primary {
  margin-top: 0;
}

.capture__cancel {
  align-self: center;
}

/* Five to eight seconds is a state with a design, not a flicker, and it happens
   roughly a hundred times during the onboarding. It is decoration beside the
   polite region that carries the words. */

.spinner {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  margin-left: var(--space-2);
  vertical-align: -0.15em;
  border: 2px solid currentcolor;
  border-top-color: transparent;
  border-radius: var(--radius-pill);
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/*
 * `onboarding.html` — the screen a person meets with nothing recorded.
 *
 * Direction C from `specs/010/design-brief.md`, chosen on the canvas: the
 * identity stands on the paper, and the whole loop — heading, three steps and
 * the no-pile sentence — is collected inside one soft panel. That is what turns
 * nine blocks of text into three objects to read, and it is also the shortest
 * of the three directions drawn, which is what keeps the action above the fold
 * at 390 × 664 even with the error banner above it.
 *
 * The one token it added is `--colour-accent-border`, up in `:root`.
 */

/* The only screen in the app with no header, so the page carries its own
   gutters rather than handing them to the bar. The column is capped well inside
   004's page width: a first-run screen stretched across a monitor is a phone
   screen on a monitor, not a page. */

.onboarding-page {
  padding: 0;
}

.onboarding {
  max-width: 34rem;
  padding: var(--space-4) var(--space-4) var(--space-7);
}

/* The banner already carries the phone's gutter for the framed screens. Here
   the screen carries it, so the panel gives it up rather than doubling it. */

.onboarding .banner {
  margin-inline: 0;
}

.onboarding .startup-failed {
  margin-inline: 0;
}

/* The identity: the drawing, the name, one sentence. Centred, and the only
   place in the app where anything is. */

.onboarding__identity {
  text-align: center;
}

/* 512 × 512 is a permanent ceiling on the source file, so 176 px is the largest
   this or any screen may draw it — an upscale invents detail in the face and
   the hands, which is most of the drawing. The box is reserved here so the page
   does not jump when the file lands. */

.onboarding__art {
  display: block;
  width: 144px;
  height: 144px;
  margin: 0 auto;
}

.onboarding__name {
  margin: var(--space-2) 0 0;
  font-size: var(--text-display);
  line-height: var(--line-tight);
  letter-spacing: -0.02em;
}

.onboarding__lede {
  margin: var(--space-1) 0 0;
  font-size: var(--text-meta);
  color: var(--colour-text-quiet);
  text-wrap: pretty;
}

/* The loop, on its own ground.
 *
 * Shown only where JavaScript is running: the ground is part of the screen's
 * shape from first paint, but with JavaScript off there is nothing to put in
 * it, and an empty coloured box is furniture pretending to be content. The mark
 * says "JavaScript is running" and stays for the life of the page, so this is
 * the same switch the boot skeletons use. */

.onboarding__loop {
  display: none;
  margin-top: var(--space-5);
  padding: var(--space-4);
  border-radius: var(--radius-large);
  background: var(--colour-accent-soft);
}

.js-booting .onboarding__loop {
  display: block;
}

.onboarding__how {
  margin: 0;
  font-size: var(--text-large);
  color: var(--colour-accent);
}

/* A real `<ol>` with real markers, so a screen reader announces a list of three
   in order without being told to. The numerals are plain figures in the accent
   and never circles: a filled circle makes a step look like a control, and the
   point of the panel is that the loop is one thing to read rather than three
   things to do. */

@counter-style onboarding-step {
  system: numeric;
  symbols: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9";
  suffix: " ";
}

.onboarding__steps {
  margin: var(--space-3) 0 0;
  padding-left: var(--space-5);
  font-size: var(--text-meta);
  list-style: onboarding-step;
}

.onboarding__steps li {
  margin-bottom: var(--space-2);
}

.onboarding__steps li:last-child {
  margin-bottom: 0;
}

.onboarding__steps li::marker {
  color: var(--colour-accent);
  font-weight: 600;
}

.onboarding__no-pile {
  margin: var(--space-3) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--colour-accent-border);
  font-size: var(--text-small);
  color: var(--colour-text-quiet);
  text-wrap: pretty;
}

/* The one control, and it is 004's own: the same words going to the same place,
   so she never meets two spellings of it. */

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

/* The by-hand route is a sentence rather than a second button, because it is
   the same destination. It reads as part of the control above it. */

.onboarding__by-hand {
  margin: var(--space-3) 0 0;
  font-size: var(--text-small);
  color: var(--colour-text-quiet);
  text-wrap: pretty;
}

/* The telling: that a picture of a band leaves the app to be read. Quiet, at
   the foot, separated by air alone — with the loop already on a panel, a rule
   or a ground of its own would make the screen three boxes and give this the
   grammar of a legal notice. It must not compete with the action; it must still
   be legible. */

.onboarding__telling {
  margin: var(--space-6) 0 0;
  font-size: var(--text-small);
  color: var(--colour-text-faint);
  text-wrap: pretty;
}

/* The shape before there is a dictionary to put words in it. The bars inside
   the panel are drawn one step darker than the ones on the paper, so they read
   against the ground they sit on rather than disappearing into it. */

.onboarding__bar {
  height: var(--space-4);
  margin-bottom: var(--space-2);
  border-radius: var(--radius-small);
  background: var(--colour-border);
}

.onboarding__bar:last-child {
  margin-bottom: 0;
}

.onboarding__bar--name {
  width: 9rem;
  height: var(--space-5);
  margin-inline: auto;
}

.onboarding__bar--part {
  width: 68%;
}

.onboarding__bar--heading {
  width: 6.5rem;
}

.onboarding__bar--step {
  width: 62%;
}

.onboarding__loop .onboarding__bar {
  background: var(--colour-accent-border);
}

/*
 * `index.html` — the door.
 *
 * Deliberately the same shape as `onboarding.html`'s identity block and not a
 * new one: the same drawing at the same 176 px ceiling, the same name at
 * display size, one sentence under it. They are the two screens with no header
 * and they are the first two things a new person sees, a minute apart, so a
 * second visual idea between them would read as two apps rather than as one.
 *
 * What it does not borrow is the soft panel. There is nothing here to collect
 * into one object — a name, a sentence and a button is already one object.
 */

.signin-page {
  padding: 0;
}

.signin {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
  max-width: 34rem;
  padding: var(--space-6) var(--space-4) var(--space-7);
}

/* The screen carries its own gutter, so the banner gives its up rather than
   doubling it — the same trade `onboarding.html` makes. */

.signin .banner {
  margin-inline: 0;
}

.signin .startup-failed {
  margin-inline: 0;
}

.signin__identity {
  text-align: center;
}

/* 512 x 512 is a permanent ceiling on the source file, so 176 px is the largest
   this or any screen may draw it — an upscale invents detail in the face and
   the hands, which is most of the drawing. The box is reserved here so the page
   does not jump when the file lands. */

.signin__art {
  display: block;
  width: 144px;
  height: 144px;
  margin: 0 auto;
}

.signin__name {
  margin: var(--space-2) 0 0;
  font-size: var(--text-display);
  line-height: var(--line-tight);
  letter-spacing: -0.02em;
}

.signin__lede {
  margin: var(--space-3) 0 0;
  font-size: var(--text-meta);
  color: var(--colour-text-quiet);
  text-wrap: pretty;
}

/* One button, full width, and the only accent on the screen. */

.signin__action {
  display: grid;
  margin-top: var(--space-6);
}

.signin__action .action__capture {
  border: none;
  cursor: pointer;
}

.signin__action .action__capture:disabled {
  cursor: default;
}

/* The shape the identity stands in while the dictionary is still on its way,
   exactly as `onboarding.html` does it: no words, and gone rather than hidden
   once the real thing arrives. */

.signin__bar {
  width: 100%;
  height: 0.75rem;
  margin: var(--space-3) auto 0;
  border-radius: var(--radius-small);
  background: var(--colour-slot);
}

.signin__bar--name {
  width: 55%;
  height: 1.5rem;
}

.signin__bar--part {
  width: 70%;
}

/* The policy link under the button, and the deployed commit under everything.
   Both are quiet on purpose: the door has one thing to do, and neither of these
   is it. */

.signin__policy {
  margin: var(--space-4) 0 0;
  text-align: center;
  font-size: var(--text-small);
}

.signin__policy a {
  color: var(--colour-text-quiet);
}

.build-stamp {
  margin: var(--space-6) 0 0;
  text-align: center;
  font-size: var(--text-small);
  color: var(--colour-text-faint);
}

/*
 * The two privacy pages, and the only documents in this app.
 *
 * They are prose rather than an interface - no header, no states, no cards -
 * so the whole treatment is a reading measure and the type scale the rest of
 * the app already uses. 34rem rather than `--measure-page`: 60rem of running
 * text is a line nobody's eye can return from.
 */

.policy {
  max-width: 34rem;
  padding-bottom: var(--space-6);
}

.policy__nav {
  display: flex;
  gap: var(--space-4);
  margin-bottom: var(--space-6);
  font-size: var(--text-small);
}

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

.policy h2 {
  margin-top: var(--space-6);
  font-size: var(--text-large);
}

.policy p {
  margin: 0 0 var(--space-4);
  text-wrap: pretty;
}

.policy__updated {
  margin-top: var(--space-6);
  font-size: var(--text-small);
  color: var(--colour-text-quiet);
}

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation-duration: 3s;
  }
}

/* Deleted by 023 and by nothing before it. Without it she spends an evening
   believing a hundred colour photographs are being recorded. */

.capture__not-kept {
  margin: var(--space-3) 0 0;
  color: var(--colour-text-quiet);
  font-size: var(--text-meta);
}

/* The sentence saying the fields below came from a picture, in the slot the
   match panel would have used.

   Inset by the card's own padding rather than sitting flush: it is a sentence
   about the fields underneath it, so it has to start where those fields start.
   Aligned to the card edge instead, it reads as a stray line belonging to
   nothing. */

.read-outcome {
  margin: 0 0 var(--space-5);
}

.read-outcome:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: 2px;
}

/* The most important panel in the app: the whole argument in one rectangle. It
   has to be unmistakably the answer to what she just did without becoming the
   loudest thing on a screen that also holds a four-section form — so it takes
   the accent's own quiet ground rather than a heavier weight or a louder hue.
   In the flow, above the form, never a dialog: the always-available empty form
   must stay reachable, and a sheet on a phone can be dismissed by accident. */

.match {
  margin: 0 0 var(--space-5);
  padding: var(--space-4);
  border: 1px solid var(--colour-accent);
  border-radius: var(--radius-large);
  background: var(--colour-accent-soft);
}

.match__title {
  margin: 0 0 var(--space-2);
  color: var(--colour-accent);
  font-size: var(--text-title);
  line-height: var(--line-tight);
}

.match__title:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: 2px;
}

/* 004's row, one size up. On the list it is one of a hundred and is scanned;
   here it is one of one and is read, carefully, because she is checking whether
   this is really the ball in her hand. */

.match__line {
  margin: 0;
  font-size: var(--text-large);
  line-height: var(--line-body);
}

/* A used-up match reads differently rather than looking different: 004 settled
   that a finished lot is drawn quietly, and this sentence is the whole visible
   difference between the two panels. */

.match__note {
  margin: var(--space-2) 0 0;
  color: var(--colour-text-quiet);
  font-size: var(--text-meta);
  line-height: var(--line-body);
}

.match .button--primary {
  margin-top: var(--space-4);
}

/* Findable when the app is wrong and quiet when it is right. Too quiet is a
   trap; too loud makes every correct match look like a question. */

.button--link {
  border: none;
  background: none;
  color: var(--colour-accent);
  font-size: var(--text-meta);
  text-decoration: underline;
}

.match__reject {
  display: block;
  width: 100%;
  margin-top: var(--space-1);
  text-align: center;
}

/* Her record wins and nothing is overwritten, so this is a note and not a
   refusal: no error colour, no `aria-invalid`, nothing blocked. It sits on the
   page ground inside the yarn's card, above the link that is the way to change
   it for every colour of that yarn at once. */

.disagrees {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-medium);
  background: var(--colour-page);
}

.disagrees__sentence {
  margin: 0 0 var(--space-2);
  color: var(--colour-text-quiet);
  font-size: var(--text-small);
  line-height: var(--line-body);
}

.disagrees__fact {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-1);
  font-size: var(--text-small);
}

.disagrees__fact:last-child {
  margin-bottom: 0;
}

.disagrees__label {
  color: var(--colour-text-quiet);
}

/* --- 009: brands.html, her list of names --------------------------------- */

/* The gutter is on the blocks rather than on the screen, exactly as 004's list
   does it: the rows' hairlines run edge to edge on a phone and the form above
   them does not. */

.brand-add {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
  padding: 0 var(--space-4) var(--space-4);
}

.brand-add__submit {
  align-self: flex-start;
}

/* One line, directly beneath the add form, and nothing at all until a write has
   happened. */

.brand-status {
  margin: 0 var(--space-4) var(--space-3);
}

.brand-status:empty {
  margin: 0;
}

/* The dot between *Gemt* and the brand's name, drawn in CSS so a screen reader
   reads two values rather than a punctuation mark — the same reason `.summary`
   draws its separators this way.

   026's colour status region is the same fact in the same shape, so it shares
   this rule rather than carrying a copy of it: both are a label, a separator
   nobody reads aloud, and the name of the row that was just written. */

.brand-status .saved__line::before,
.colour-status .saved__line::before {
  content: "·";
  margin-right: var(--space-1);
  color: var(--colour-text-faint);
}

/* A banner naming what it is about carries the name on a second line rather
   than inside the sentence: Danish and English would not agree about where it
   goes. */

.banner__sentence,
.banner__line {
  margin: 0;
}

.banner__line {
  margin-top: var(--space-1);
  font-weight: 600;
}

.brand-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Not a `<table>`: two of the three things in a row are controls, and at 320 px
   the row wraps to two lines — the name on the first, the count and the two
   verbs on the second — which is a layout a table cannot have. */

.brand-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--colour-border-soft);
}

/* Nothing groups these rows: every one is a brand and the name is the whole of
   its identity, so 004's rule puts the emphasis here and everything else on the
   row is quieter than it. A long name wraps rather than truncating — a
   truncated name on the screen for fixing names is a joke at her expense. */

.brand-row__name {
  flex: 1 1 100%;
  font-size: var(--text-body);
  font-weight: 600;
  color: var(--colour-text);
  overflow-wrap: break-word;
}

.brand-row__count {
  flex: 1 1 auto;
  font-size: var(--text-meta);
  color: var(--colour-text-quiet);
}

.brand-row__actions {
  display: flex;
  gap: var(--space-2);
}

/* Words, never icons: a pencil and a bin would be the two most consequential
   controls on the screen drawn as pictures nobody can be sure of. Quiet text
   controls that gain their border on hover and focus rather than wearing one
   down the whole list. */

.button-text {
  min-height: var(--tap-target);
  padding: 0 var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-medium);
  background: transparent;
  color: var(--colour-accent);
  font: inherit;
  font-size: var(--text-meta);
  cursor: pointer;
}

.button-text:hover:not(:disabled) {
  border-color: var(--colour-border);
  background: var(--colour-surface);
}

.button-text:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: -2px;
}

.button-text:disabled {
  color: var(--colour-text-faint);
  cursor: default;
}

/* An open row is the whole row and nothing else on the screen repaints: on a
   phone a rebuilt text field loses the caret. */

.brand-row--editing,
.brand-row--confirming {
  display: block;
}

.brand-rename {
  margin: 0;
}

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

.brand-rename__buttons .button {
  flex: 1 1 8rem;
}

/* The link lists on the dev pages - `reads.html`'s eleven stubbed reads and
   `states.html`'s forced failures. One block rather than two because they are
   the same list doing the same job, and both go with the mock in 024. */

.devlinks {
  margin: 0;
  padding: 0;
  list-style: none;
}

.devlinks__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--colour-border-soft);
}

.devlinks__link {
  color: var(--colour-accent);
}

.devlinks__parameter {
  color: var(--colour-text-faint);
  font-size: var(--text-small);
}

/* --- 026: yarn.html, one yarn and everything under it -------------------- */

/*
 * The one visual question this screen asks, and the answer it got.
 *
 * Three levels on a 390 px phone — a yarn, its colours, their lots — must read
 * as *a list of colours she owns* and not as an outline, a file tree or an admin
 * panel. That last is the failure mode named in the design brief, and it is the
 * feeling she rejected before she rejected Ravelry.
 *
 * **Nothing is a card and nothing is nested in a box.** A colour is separated
 * from the next one by a hairline running edge to edge and by whitespace, which
 * is exactly how 004's list separates its rows — so the two screens use one
 * idiom rather than two. Inside a colour, the lot rows carry no rules at all:
 * they are held together by being indented under a heading that is louder than
 * they are, which is 004's grouping rule one level up. A second border inside
 * the first is what turns indentation into an outline.
 *
 * The gutter is on each block rather than on the screen, again as 004 does it,
 * because the colours' hairlines have to reach both edges of the phone while
 * the form above them does not.
 */

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

/* Not the name of a task — this screen is *about* a yarn. Sized like 007's
   identity rather than 006's page title: two names joined at a display size is
   two lines on a phone. */

.yarn__title {
  margin: 0;
  padding: var(--space-4) 0 var(--space-3);
  font-size: var(--text-title);
  font-weight: 600;
  line-height: var(--line-tight);
  letter-spacing: -0.01em;
  overflow-wrap: break-word;
}

/* One column at every width, per 007: a two-column form asks her to read in a
   Z, and this one is answered downward. */

.yarn-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin: 0;
}

/* The brand when her brand list could not be read: the name the yarn already
   carries, as text. Set like a field's value rather than like a disabled
   control — there is no control, and one that looked answerable would be the
   lie 005 refused for the filter bar. */

.field__readonly {
  min-height: var(--tap-target);
  display: flex;
  align-items: center;
  color: var(--colour-text-quiet);
  font-size: var(--text-meta);
}

/* 007's saved line, and nothing beside the word: the six values she changed are
   in the fields above it and in the `<h1>`, both repainted from the row that
   came back. */

.yarn-saved {
  margin: var(--space-4) 0 0;
}

/*
 * The seam between the form and the list — the design brief's Open 4.
 *
 * Two genuinely different objects on one screen: a form she fills in, and a list
 * she reads. *Farver* is the only visible section heading in the app, and it
 * earns that by naming a block which has no label of its own; the extra space
 * above it and the rule at the top of the first colour are what stop the
 * colours reading as a seventh field.
 */

.colours-heading {
  margin: var(--space-7) 0 0;
  padding: 0 var(--space-4);
  font-size: var(--text-large);
  font-weight: 600;
}

.colours-heading:focus-visible {
  outline: 2px solid var(--colour-accent);
  outline-offset: 2px;
}

.colour-status {
  margin: var(--space-2) var(--space-4) 0;
}

.colour-status:empty {
  margin: 0;
}

.colour-list {
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
}

/* The hairline and the whitespace that separate one colour from the next, and
   the whole of what separates them. Edge to edge on a phone, exactly as 004's
   rows are. */

.colour {
  padding: var(--space-4) 0 var(--space-5);
  border-top: 1px solid var(--colour-border);
}

/*
 * The heading and its two verbs, on one line where there is room and two lines
 * where there is not — the design brief's Open 2 and Open 9. Wrapping rather
 * than shrinking, so at 320 px the name takes the first line and the buttons
 * the second, and every button keeps its tap target either way.
 *
 * Quiet text buttons that gain their border on hover and focus, which is 009's
 * answer to the same ratio problem: four colours means eight buttons, and eight
 * bordered buttons is a toolbar.
 */

.colour__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
  padding: 0 var(--space-4);
}

/* The two open states stand in the place of the heading *and* its buttons, so
   each takes the whole width of the head rather than a share of one line. */

.colour__head > .colour-edit,
.colour__head > .confirm {
  flex: 1 1 100%;
}

.colour__name {
  flex: 1 1 11rem;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  margin: 0;
  font-size: var(--text-body);
  font-weight: 600;
  line-height: var(--line-tight);
}

/* The colour is what groups the rows, so the colour carries the emphasis —
   `CLAUDE.md`'s lesson from 004's real list, one level up. A long name wraps
   rather than truncating: a truncated name on the screen for fixing names is a
   joke at her expense. */

.colour__label {
  color: var(--colour-text);
  overflow-wrap: anywhere;
}

/* Quiet beside the name, and drawn only when it is not already the name. The
   separator is CSS so a screen reader reads two values rather than a
   punctuation mark. */

.colour__code {
  color: var(--colour-text-quiet);
  font-size: var(--text-meta);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

.colour__code::before {
  content: "·";
  margin-right: var(--space-1);
  color: var(--colour-text-faint);
}

/* The negative end margin gives the last button's own padding back to the
   gutter, so the words line up with the edge the rest of the block keeps. */

.colour__actions {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-inline-end: calc(var(--space-3) * -1);
}

/*
 * The lots of one colour: indented under it, quieter than it, and separated by
 * nothing. Not a `<table>` — at 320 px each row keeps its number and quantity
 * on one line and the reservation wraps below them, which a table cannot do.
 */

.lot-list {
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
}

.lot-row__link {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0 var(--space-3);
  padding: var(--space-2) var(--space-4) var(--space-2) var(--space-6);
  color: inherit;
  text-decoration: none;
}

.lot-row__link:hover {
  background: var(--colour-surface);
}

.lot-row__link:focus-visible {
  outline-offset: -2px;
}

/* Plain text at the same size as the quantity beside it, by her decision on
   004's real list. `--text-lot` does not appear on this screen: 007 is its one
   reader, where she has arrived at a single lot and there are no rows to group.
   Tabular figures stay — that is columns lining up, not emphasis. */

.lot-row__number {
  color: var(--colour-text-quiet);
  font-size: var(--text-meta);
  font-variant-numeric: tabular-nums;
}

.lot-row__quantity {
  color: var(--colour-text-quiet);
  font-size: var(--text-meta);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.lot-row__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  flex-basis: 100%;
  margin-top: var(--space-1);
}

/* 004's badge and note, reused unchanged: a reservation is a fact about whether
   the yarn is spoken for, and this is a list of yarn. */

.lot-row__badge {
  padding: 0 var(--space-2);
  border: 1px solid var(--colour-reserved-border);
  border-radius: var(--radius-pill);
  background: var(--colour-reserved-surface);
  color: var(--colour-reserved);
  font-size: var(--text-small);
  font-weight: 600;
}

.lot-row__note {
  color: var(--colour-reserved);
  font-size: var(--text-small);
}

/* Two of 004's three exceptions, marked the two ways 004 marks them, because
   they are not the same kind of thing. A state she acts on gets a rule in the
   margin; a measurement nobody has taken yet reads as unfinished rather than as
   a number. Anything louder than this turns the block into an error log. */

.lot-row--reserved {
  box-shadow: inset 3px 0 0 var(--colour-reserved);
}

.lot-row--unweighed .lot-row__quantity {
  font-style: italic;
}

/*
 * The count of what she cannot see — the design brief's Open 5.
 *
 * Quieter than a lot row, below the rows and above the *Tilføj parti* link. Not
 * a badge, not `--colour-error`, and not a row itself: this is a state she acts
 * on elsewhere rather than an exception. It exists so the number in the delete
 * confirmation is never a surprise.
 */

.colour__used-up {
  margin: var(--space-2) 0 0;
  padding: 0 var(--space-4) 0 var(--space-6);
  color: var(--colour-text-quiet);
  font-size: var(--text-small);
}

/*
 * The one control of the three she uses often — the design brief's Open 3.
 *
 * A link and not a button, because it goes somewhere, and inside the colour's
 * indentation so it reads as belonging to the colour above it rather than
 * floating between two of them. Its own line, with a tap target, because it sits
 * where a thumb arrives after reading the rows.
 */

.colour__add {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  margin: 0 var(--space-4) 0 var(--space-6);
  color: var(--colour-accent);
  font-size: var(--text-small);
}

/* An open colour is its head and nothing else on the screen: on a phone a
   rebuilt text field loses the caret, and the lot rows below do not move. */

.colour-edit {
  margin: 0;
}

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

.colour-edit__buttons .button {
  flex: 1 1 8rem;
}

/* A count inside a confirmation, on a line of its own so it can take its own
   plural. Louder than the identity above it, because it is the fact she is
   least likely to already know — the design brief's Open 11. */

.confirm__count {
  margin: 0 0 var(--space-3);
  font-size: var(--text-meta);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/*
 * *Slet garnet*, at the foot of a long scroll — the design brief's Open 6.
 *
 * Last on the screen and last in the tab order, so she reaches everything she
 * came for before she reaches the one thing that cannot be undone, and so the
 * rows it will take are the last thing she reads before she taps it. 007's
 * treatment for `.lot-actions`: quiet, above a rule, full width — findable
 * without being what she sees when she arrives.
 */

.yarn-delete {
  margin: var(--space-6) var(--space-4) 0;
  padding-top: var(--space-5);
  border-top: 1px solid var(--colour-border);
}

.yarn-delete:empty {
  margin: 0;
  padding: 0;
  border-top: none;
}

/* 025 — the administrator's screen. Nothing here is new furniture: the add
   form, the status line, the banner and the confirmation are 009's, reused
   rather than restyled, and what follows is only the two lists this screen has
   that no other screen has. */

.invitee-add {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
  padding: 0 var(--space-4) var(--space-4);
}

.invitee-add__submit {
  align-self: flex-start;
}

/* One line, directly beneath the add form, and nothing at all until a write has
   happened. The dot between the label and the address is drawn in CSS so a
   screen reader reads two values rather than a punctuation mark — the same
   reason `.brand-status` and `.summary` draw their separators this way. */

.admin-status {
  margin: 0 var(--space-4) var(--space-3);
}

.admin-status:empty {
  margin: 0;
}

.admin-status .saved__line::before {
  content: "·";
  margin-right: var(--space-1);
  color: var(--colour-text-faint);
}

/* Both headings are the same size and weight, because the list and the history
   are two answers to one question rather than a thing and its footnote. */

.invitees__heading {
  margin: var(--space-5) 0 var(--space-2);
  padding: 0 var(--space-4);
  font-size: var(--text-meta);
  font-weight: 600;
  color: var(--colour-text-quiet);
}

.invitee-list,
.event-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Not a `<table>`: the third thing in a row is a control, and at 320 px the row
   wraps to two lines — the address on the first, the note and the verb on the
   second. The address is what identifies the row and nothing groups these rows,
   so 004's rule puts the emphasis there and the rest of the row is quieter. */

.invitee-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-bottom: 1px solid var(--colour-border-soft);
}

/* `anywhere` rather than `break-word`: an address is one long token with no
   spaces to break at, and a truncated address on the screen for deciding who
   may sign in is a joke at his expense. */

.invitee-row__email {
  flex: 1 1 100%;
  font-size: var(--text-body);
  font-weight: 600;
  color: var(--colour-text);
  overflow-wrap: anywhere;
}

.invitee-row__note {
  flex: 1 1 auto;
  font-size: var(--text-meta);
  color: var(--colour-text-quiet);
}

.invitee-row__actions {
  display: flex;
  gap: var(--space-2);
}

/* An open confirmation is the whole row and nothing else on the screen
   repaints: on a phone a rebuilt control loses the focus ring, and the field
   above this list is where he was standing. */

.invitee-row--confirming {
  display: block;
}

/* The sentence saying nobody else is invited yet, which stands in for an empty
   state this list cannot reach: allowed_email always holds the administrator
   and his row cannot be deleted. It is not an error and must not read as one —
   `--colour-error` belongs nowhere near it. */

.invitees__nobody-else {
  margin: var(--space-3) 0 0;
  padding: 0 var(--space-4);
  font-size: var(--text-meta);
  color: var(--colour-text-quiet);
}

/* The history is a record and not a list he acts on, so every part of it is
   quieter than an invitee row — including the address, which here is one of
   three facts about a past event rather than the identity of a row. */

.event-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  padding: var(--space-1) var(--space-4);
  font-size: var(--text-small);
  color: var(--colour-text-quiet);
}

.event-row__email {
  overflow-wrap: anywhere;
}

.event-row__action {
  color: var(--colour-text);
}

.event-row__at {
  flex: 1 1 auto;
  text-align: right;
  font-size: var(--text-micro);
  color: var(--colour-text-faint);
}

@media (min-width: 48rem) {
  .app .screen {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    column-gap: var(--space-6);
    padding: 0 var(--space-5) var(--space-7);
  }

  /* 006's form is one column at every width. A two-column form asks her to
     read in a Z rather than down, and this one's whole job is to be answered in
     order. */

  .app .screen--form {
    display: block;
    padding: 0 var(--space-5) var(--space-7);
  }

  .page-title {
    padding: var(--space-5) 0 var(--space-3);
  }

  .banner,
  .add-form,
  .lede {
    margin-inline: 0;
  }

  .add {
    padding-inline: 0;
  }

  /* 007's form is one column too. The screen's own padding takes over here, so
     the block holding the phone's gutter gives it up and the identity block
     above the form keeps only its vertical rhythm. */

  .lot {
    padding-inline: 0;
  }

  .lot-identity {
    padding: var(--space-5) 0 var(--space-3);
  }

  /* 009's screen is one column too, and at this width the row fits on one
     line. The screen's own padding takes over, so the blocks holding the
     phone's gutter give it up. */

  .brand-add,
  .brand-row {
    padding-inline: 0;
  }

  .brand-status {
    margin-inline: 0;
  }

  /*
   * 026 stays one column too — the design brief's Open 8, answered with its own
   * default.
   *
   * This is the first screen in the app with two separable blocks on it, so it
   * is the first place a second column is even arguable: the form on the left,
   * the colours on the right. It stays one column because the two blocks are
   * read in order rather than compared — she corrects the fibre, then looks at
   * the colours — and because a form beside a list is a screen whose two halves
   * scroll independently on a laptop and not on a phone, which is two layouts
   * to keep agreeing rather than one.
   *
   * The screen's own padding takes over, so every block holding the phone's
   * gutter gives it up. The lot rows keep their indent, because that indent is
   * what makes them belong to the colour above them at any width.
   */

  .yarn {
    padding-inline: 0;
  }

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

  .colours-heading,
  .colour__head {
    padding-inline: 0;
  }

  .colour-status,
  .yarn-delete {
    margin-inline: 0;
  }

  .lot-row__link {
    padding-inline: var(--space-5) var(--space-2);
  }

  .colour__used-up {
    padding-inline: var(--space-5) 0;
  }

  .colour__add {
    margin-inline: var(--space-5) 0;
  }

  .brand-row__name {
    flex: 1 1 auto;
  }

  .brand-row__count {
    flex: 0 0 auto;
  }

  /* Explicit rows, because the bar sits between the action and the summary in
     the DOM — the reading and tab order — while spanning the width they share
     a line across. */

  .action {
    grid-column: 1;
    grid-row: 1;
    padding: var(--space-5) 0 var(--space-4);
  }

  .action__capture {
    padding: 0 var(--space-5);
  }

  .summary {
    grid-column: 2;
    grid-row: 1;
    justify-self: end;
    padding: var(--space-5) 0 var(--space-4);
    text-align: right;
  }

  .summary__items {
    justify-content: flex-end;
  }

  .filter {
    grid-column: 1 / -1;
    grid-row: 2;
    padding: 0 0 var(--space-4);
  }

  /* One line, with the box given the room: it is the shop lookup and the
     most-used control on the screen. */

  .filter-bar {
    flex-direction: row;
    align-items: center;
    gap: var(--space-3);
  }

  .filter-bar__search {
    flex: 2 1 16rem;
  }

  .filter-bar__controls {
    flex: 3 1 24rem;
    flex-wrap: nowrap;
    gap: var(--space-3);
  }

  .content {
    grid-column: 1 / -1;
    grid-row: 3;
  }

  .app .state {
    margin: var(--space-4) 0;
  }

  .stash-row__link {
    display: grid;
    grid-template-columns: var(--photo-slot) minmax(0, 1fr) 7rem 9rem 12rem;
    align-items: center;
    gap: var(--space-5);
    padding: var(--space-2);
    border-radius: var(--radius-medium);
  }

  .stash-row__body,
  .stash-row__main {
    display: contents;
  }

  .stash-row__identity {
    font-size: var(--text-meta);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .stash-row__tags {
    margin-top: 0;
    flex-wrap: nowrap;
    min-width: 0;
  }

  .stash-row__note {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* 010 at desktop widths: a centred page rather than a stretched phone
     column. The drawing takes the full 176 px here because there is room for
     it — and 176 px is the ceiling, not a step on the way to a larger one. */

  .onboarding {
    padding: var(--space-7) var(--space-5) var(--space-7);
  }

  .onboarding__art,
  .signin__art {
    width: 176px;
    height: 176px;
  }

  .signin {
    padding: var(--space-7) var(--space-5);
  }

  .signin__lede {
    font-size: var(--text-body);
  }

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

  .onboarding__lede {
    font-size: var(--text-body);
  }

  .onboarding__loop {
    margin-top: var(--space-6);
    padding: var(--space-5);
  }

  .onboarding__steps {
    font-size: var(--text-body);
  }

  .onboarding__steps li {
    margin-bottom: var(--space-3);
  }

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