/*
 * Matplan — mobile first, and meant literally.
 *
 * Three of the four screens are used standing up: at the counter with wet
 * hands, in a shop with a trolley in one hand, at the table planning the week.
 * So: one column, large type, generous tap targets, and nothing that depends on
 * hover — a hover affordance on a touch screen is an affordance that does not
 * exist.
 *
 * Colours follow the device. A kitchen at 18:00 in November is dark and a shop
 * is fluorescent; neither is a preference we should override.
 */

/*
 * Three states, not two.
 *
 * "System" is real and it is the default: nothing stamped on <html> means
 * `prefers-color-scheme` decides. An explicit choice from the header toggle is
 * stamped as `data-theme` and must win in *both* directions, which is why the
 * dark palette is written twice — once behind the media query, guarded so an
 * explicit light choice still wins on a dark OS, and once behind the attribute
 * so an explicit dark choice wins on a light one.
 *
 * A token defined only inside the media query would never reach somebody who
 * chose dark on a light system, which is the ordinary way this ends up with one
 * theme's text on the other theme's background.
 */
:root {
  color-scheme: light;

  --bg: #fbfaf7;
  --surface: #ffffff;
  --border: #e2ddd3;
  --text: #1c1a17;
  --muted: #6b655c;
  --accent: #146b4a;
  --accent-text: #ffffff;
  --danger: #a3261c;
  --warning-bg: #fdf3d8;
  --scrim: rgba(28, 26, 23, 0.42);

  --radius: 12px;
  --tap: 48px;              /* never smaller: a thumb is about 45px */
  --topbar: calc(44px + 0.8rem + 1px);   /* button + padding + border */
  --gap: 1rem;
  --measure: 34rem;         /* one comfortable column */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --bg: #16150f;
    --surface: #211f18;
    --border: #383429;
    --text: #f0ece2;
    --muted: #a49c8c;
    --accent: #4fbf8b;
    --accent-text: #0d1a13;
    --danger: #ef8279;
    --warning-bg: #2f2a16;
    --scrim: rgba(0, 0, 0, 0.6);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #16150f;
  --surface: #211f18;
  --border: #383429;
  --text: #f0ece2;
  --muted: #a49c8c;
  --accent: #4fbf8b;
  --accent-text: #0d1a13;
  --danger: #ef8279;
  --warning-bg: #2f2a16;
  --scrim: rgba(0, 0, 0, 0.6);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font: 1.05rem/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* Safe areas, because this is used full-screen on a phone.
     The *top* inset is deliberately not here: a sticky header inside a padded
     body sticks to the top of the scrollport, which on a notched phone in
     standalone mode is underneath the notch. So the top inset belongs to
     whatever is actually at the top — the header when there is one, main when
     there is not. */
  padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}

main {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 1.5rem 1.15rem 5rem;
}

/*
 * A page with no chrome on it — sign in, the code form, "check your e-mail" —
 * is a card on an empty screen, and pinned to the top it reads as a page that
 * failed to finish loading. Centre it, the way newsupdate's sign-in shell does.
 *
 * `margin: auto` on the child rather than `align-items: center` on the flex
 * container: they centre the same way, but a centred flex item taller than the
 * viewport has its overflow cut off at the top and cannot be scrolled to.
 * An auto margin does not do that, and the code form on a small landscape
 * screen is exactly tall enough for it to matter.
 */
body.centred {
  display: flex;
  min-height: 100svh;
  padding-top: env(safe-area-inset-top);
}
body.centred main { margin: auto; width: 100%; padding-bottom: 1.5rem; }

h1 { font-size: 1.6rem; line-height: 1.2; margin: 0 0 0.25rem; }
h2 { font-size: 1.2rem; margin: 2rem 0 0.5rem; }
p  { margin: 0 0 1rem; }
a  { color: var(--accent); }

.lead { color: var(--muted); margin-bottom: 2rem; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.15rem;
  margin-bottom: var(--gap);
}
/* The first thing in a card never adds space above itself — the padding is
   already there. This used to be `style="margin-top:0"` on twenty-two headings,
   which is the same rule written twenty-two times, and it is written here now
   because the Content-Security-Policy forbids style attributes: `style-src
   'self'` with no 'unsafe-inline' is what makes the policy worth having, and
   every inline style is a reason to weaken it. */
.card > :first-child { margin-top: 0; }

label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.4rem;
}

/*
 * Every text-like field, named by what it is not rather than by listing the
 * types that happen to exist today.
 *
 * It was a list — email, text, number — and `url`, `date` and `password` were
 * never on it, so those three kept the browser's own colours. That was
 * invisible for as long as the theme always followed the OS, because the
 * browser's colours followed it too. It stops being invisible the moment the
 * header gains a toggle: choose dark on a light system and a url field on
 * /oppskrifter is a light box in a dark form.
 *
 * Every excluded type either has its own rule further down or is not a field.
 */
input:not([type="checkbox"], [type="radio"], [type="file"],
          [type="hidden"], [type="submit"], [type="button"]),
select,
textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 0.6rem 0.75rem;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* 16px minimum, or iOS zooms the whole page on focus. */
input, select, textarea { font-size: max(1rem, 16px); }

button, .button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: var(--tap);
  width: 100%;
  padding: 0.7rem 1.1rem;
  font: inherit;
  font-weight: 600;
  color: var(--accent-text);
  background: var(--accent);
  border: 1px solid transparent;
  border-radius: var(--radius);
  text-decoration: none;
  cursor: pointer;
}

button.secondary, .button.secondary {
  color: var(--text);
  background: transparent;
  border-color: var(--border);
}

.notice, .error {
  padding: 0.85rem 1rem;
  border-radius: var(--radius);
  margin-bottom: var(--gap);
  background: var(--warning-bg);
  border: 1px solid var(--border);
}
.error { color: var(--danger); }

.muted { color: var(--muted); font-size: 0.92rem; }

/*
 * The passkey affordance on the sign-in page: below the box, quiet, and
 * unmistakably a button.
 *
 * Not a link — a link reads as navigation, and this does something. That
 * sentence is lifted from newsupdate's auth-ui.js, where it records a bug that
 * has already been paid for once.
 *
 * It is still a full-height tap target, because this app is used one-handed:
 * "quiet" is about weight, not about size.
 */
.alt { margin: 0.25rem 0 1.5rem; text-align: center; }
button.ghost {
  width: auto;
  min-height: var(--tap);
  padding: 0.6rem 1.1rem;
  font-weight: 600;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
}
button.ghost svg {
  flex: none;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
button.ghost:disabled { opacity: 0.55; cursor: progress; }
.alt .muted { margin: 0.6rem 0 0; text-align: left; }
/* passnokkel.js sets the class to one or the other depending on whether the
   prompt failed or was simply cancelled. */
.alt .error, .alt .notice { margin: 0.75rem 0 0; text-align: left; }

/* The magic link, shown on screen only when mail is not configured. */
.devlink {
  display: block;
  word-break: break-all;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
}

/*
 * The application chrome: one persistent bar, two overlays behind it.
 *
 *     [≡]            Matplan            [◐] [MJ]
 *
 * Three grid columns rather than space-between, so the brand is centred on the
 * *page* and not in whatever space the two ends happen to leave — the right-hand
 * group is two buttons wide and the left is one, and space-between put the name
 * visibly off-centre.
 *
 * Sticky, because the drawer is now the only way between screens and a way out
 * you have to scroll up to find is one people stop using.
 */
.topbar {
  position: sticky;
  top: 0;
  /* Over the scrim at 25, so every control here stays clickable while an
     overlay is open and pressing a button again closes what it opened. The
     stack is written out above `.scrim`. */
  z-index: 40;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.5rem;
  /* Its own top inset — see the note on body's padding.
     The shorthand comes first and the top is set after it. The other way round,
     which is how it stood until the drawer's offset had to be measured against
     a real header, the shorthand quietly reset the inset to 0.4rem: on a
     notched phone in standalone mode the header sat under the notch, and the
     profile menu — which offsets itself by `--topbar + env(safe-area-inset-top)`
     — opened a notch's height below the header it hangs from. */
  padding: 0.4rem 0.75rem;
  padding-top: calc(0.4rem + env(safe-area-inset-top));
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.topbar .brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
  text-decoration: none;
  color: var(--text);
  white-space: nowrap;
}
.topbar .brand img { display: block; border-radius: 6px; }
.topend { display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem; }

/* The header's buttons are the exception to the full-width tap target below:
   they are square, and they still clear 44px. */
.iconbtn,
.avatar {
  flex: none;
  width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.iconbtn {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 10px;
  color: var(--text);
}
.iconbtn svg { display: block; }
.avatar {
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-text);
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* Both overlays share one scrim, so opening either closes the other by
   construction rather than by remembering to.

   The stack, which is what msjpf/newsupdate#7 was about:

       topbar 40  — over the scrim, so its controls stay clickable
       panels 30  — over the page, under the header
       scrim  25  — over the page, under both

   The drawer used to start at top:0, which put it on top of the hamburger that
   opens it. A second press landed on the drawer's own heading and did nothing
   at all — not press-again, and not the scrim's close either, since the drawer
   sits over the scrim too. Raising the header alone would not have fixed that:
   the drawer covers the top-left corner however the header is stacked.

   The scrim keeps inset:0 and passes under the header rather than being cut to
   start below it. The header is opaque, so nothing shows through, and one fewer
   number has to agree with the header's height. The header is no longer part of
   the dismiss surface, which is the trade: the whole page below it still closes
   on a click, and so do Escape and the button itself. */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 25;
  background: var(--scrim);
}
.scrim[hidden], .drawer[hidden], .menu[hidden] { display: none; }

.drawer {
  position: fixed;
  /* Under the header, not over it, so #menubtn is never covered by what it
     opens. Same expression as `.menu` below, deliberately: the two panels hang
     off the same edge and a header height that only half of them agree with is
     the drift this fix exists to remove. */
  top: calc(var(--topbar) + env(safe-area-inset-top));
  left: 0;
  bottom: 0;
  z-index: 30;
  width: min(17rem, 82vw);
  padding: 0.6rem 0.6rem calc(0.6rem + env(safe-area-inset-bottom));
  padding-left: calc(0.6rem + env(safe-area-inset-left));
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border-right: 1px solid var(--border);
}
.drawer .hd, .menu .who {
  margin: 0 0 0.3rem;
  padding: 0.6rem 0.7rem 0.4rem;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
}
.drawer a {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 0.7rem;
  border-radius: 10px;
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
}
.drawer a[aria-current="page"], .menu a[aria-current="page"] {
  background: var(--accent);
  color: var(--accent-text);
}

/* Anchored under the avatar rather than filling the screen: this is three
   items, and a full-height panel for three reads as somewhere you have gone. */
.menu {
  position: fixed;
  top: calc(var(--topbar) + env(safe-area-inset-top));
  right: calc(0.6rem + env(safe-area-inset-right));
  z-index: 30;
  width: min(16rem, calc(100vw - 1.2rem));
  padding: 0.4rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);
}
.menu .who {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  text-transform: none;
  letter-spacing: 0;
  border-bottom: 1px solid var(--border);
}
.menu .who b { font-size: 0.95rem; color: var(--text); overflow-wrap: anywhere; }
.menu .who span { font-size: 0.8rem; overflow-wrap: anywhere; }
.menu a, .menu button {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  width: 100%;
  min-height: var(--tap);
  padding: 0 0.7rem;
  border: 0;
  border-radius: 10px;
  background: none;
  color: var(--text);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
}
.menu hr { border: 0; border-top: 1px solid var(--border); margin: 0.3rem 0; }
.menu form { margin: 0; }

/* Min side: members and what they cannot eat. */

.rowhead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}
.rowhead .muted { font-size: 0.85rem; }

ul.hensyn { list-style: none; margin: 0.5rem 0; padding: 0; }
ul.hensyn li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0;
  border-top: 1px solid var(--border);
}
ul.hensyn li:first-child { border-top: 0; }
ul.hensyn form { margin-left: auto; }

.tag {
  flex: none;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: var(--border);
  color: var(--muted);
}
/* A hard limit reads as a hard limit. An allergy shown the same weight as a
   dislike is an allergy somebody skims past. */
.tag.strict {
  background: var(--danger);
  color: #fff;
}

/* An inline destructive action. Still a real tap target — 44px of padded area
   around a small label, rather than a small label. */
button.linky {
  width: auto;
  min-height: auto;
  padding: 0.55rem 0.4rem;
  margin: -0.55rem 0;
  background: none;
  border: 0;
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: underline;
}

details { margin: 0.5rem 0; }
summary {
  min-height: var(--tap);
  display: flex;
  align-items: center;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
}
details[open] summary { margin-bottom: 0.5rem; }

/* Recipes. */

.actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: var(--gap); }
.actions > * { flex: 1 1 12rem; }
.actions form { display: flex; }
.actions form button { width: 100%; }

.searchbar { margin-bottom: var(--gap); }

ul.cards { list-style: none; margin: 0 0 1rem; padding: 0; }
ul.cards li { margin-bottom: 0.6rem; }
ul.cards a {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-height: var(--tap);
  padding: 0.85rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  text-decoration: none;
}
ul.cards .muted { font-size: 0.85rem; }

ul.ingredients { list-style: none; margin: 0.75rem 0 0; padding: 0; }
ul.ingredients li {
  display: flex;
  gap: 0.6rem;
  padding: 0.55rem 0;
  border-top: 1px solid var(--border);
}
ul.ingredients li:first-child { border-top: 0; }
/* The number in its own column, so the eye runs down the amounts rather than
   hunting for them inside a sentence. Tabular figures so they line up. */
.amount {
  flex: none;
  min-width: 4.5rem;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.scaler { display: flex; align-items: flex-end; gap: 0.6rem; flex-wrap: wrap; }
.scaler label { flex: 1 1 100%; margin-bottom: 0; }
.scaler input { flex: 1 1 6rem; }
.scaler button { flex: 0 0 auto; width: auto; }

textarea { min-height: 8rem; resize: vertical; line-height: 1.5; }

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

/* KI and usage. */

.stats { display: flex; flex-wrap: wrap; gap: 1.25rem; margin-bottom: 0.5rem; }
.stats > div { display: flex; flex-direction: column; }
.stat { font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.stats .muted { font-size: 0.85rem; }

/* Tables scroll inside their card rather than making the page scroll sideways,
   which on a phone is the difference between usable and not. */
.card table { width: 100%; border-collapse: collapse; display: block; overflow-x: auto; }
.card th, .card td {
  text-align: left;
  padding: 0.5rem 0.6rem 0.5rem 0;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.card th { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
.card td:last-child, .card th:last-child { text-align: right; padding-right: 0; font-variant-numeric: tabular-nums; }

/* Plan quota. A bar, because "83 %" means more next to a bar than alone. */

.quota { margin: 0.9rem 0; }
.quota-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem; }
.quota-pct { font-weight: 700; font-variant-numeric: tabular-nums; }
.quota .muted { font-size: 0.82rem; margin: 0.25rem 0 0; }

.meter {
  height: 0.6rem;
  margin-top: 0.35rem;
  background: var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.meter span {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
}
/* Colour only near the top. A bar that reddens at 40% teaches people to ignore
   it, which is the opposite of what a quota warning is for. */
.meter span.mid { background: #b07d18; }
.meter span.high { background: var(--danger); }
/* How full the bar is, in steps of five.
   A width has to come from the data, and the obvious way to write that is a
   style attribute — which is the one thing `style-src 'self'` refuses. Nor is a
   custom property a way round it: `style="--fill: 42%"` is still a style
   attribute. So the value is rounded to a step and named as a class.
   Five points is not a loss: the exact number is printed beside the bar, and a
   quota meter is read as "nearly full" or "plenty left" rather than measured. */
.meter span.w0 { width: 0%; }
.meter span.w5 { width: 5%; }
.meter span.w10 { width: 10%; }
.meter span.w15 { width: 15%; }
.meter span.w20 { width: 20%; }
.meter span.w25 { width: 25%; }
.meter span.w30 { width: 30%; }
.meter span.w35 { width: 35%; }
.meter span.w40 { width: 40%; }
.meter span.w45 { width: 45%; }
.meter span.w50 { width: 50%; }
.meter span.w55 { width: 55%; }
.meter span.w60 { width: 60%; }
.meter span.w65 { width: 65%; }
.meter span.w70 { width: 70%; }
.meter span.w75 { width: 75%; }
.meter span.w80 { width: 80%; }
.meter span.w85 { width: 85%; }
.meter span.w90 { width: 90%; }
.meter span.w95 { width: 95%; }
.meter span.w100 { width: 100%; }

code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9em; }

/* Planlegg. */

.weeknav { display: flex; align-items: center; gap: 0.6rem; margin-bottom: var(--gap); }
.weeknav .button { flex: 0 0 auto; width: auto; padding: 0.5rem 0.9rem; }
.weeklabel { flex: 1; text-align: center; font-weight: 600; }

.card.day .dish { font-size: 1.1rem; font-weight: 600; margin: 0.2rem 0 0.3rem; }
.card.day form { display: inline; }

/* A flagged suggestion has to look different from three feet away, because that
   is the distance at which someone decides whether to read it. */
.card.flagged { border-color: var(--danger); border-width: 2px; }

label.check {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  min-height: var(--tap);
  font-weight: 400;
  cursor: pointer;
}
label.check input[type="checkbox"] {
  flex: none;
  width: 1.4rem;
  height: 1.4rem;
  margin-top: 0.15rem;
  accent-color: var(--accent);
}

/* Handle. The one screen used with a trolley in one hand and gloves on. */

ul.shoplist { list-style: none; margin: 0; padding: 0; }
ul.shoplist li { border-top: 1px solid var(--border); }
ul.shoplist li:first-child { border-top: 0; }
ul.shoplist form { display: block; }

/* The whole row is the tap target, not a checkbox inside it. */
button.tick {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  width: 100%;
  min-height: 3.4rem;
  padding: 0.6rem 0;
  background: none;
  border: 0;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.tick .box {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  border: 2px solid var(--border);
  border-radius: 7px;
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--accent-text);
}
.tick .what { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem; }
.tick .name { font-size: 1.05rem; }
.tick .muted { font-size: 0.8rem; width: 100%; }

/* Ticked lines stay visible but recede — crossing them out is how you see what
   is left without losing your place. */
ul.shoplist li.done .tick { color: var(--muted); }
ul.shoplist li.done .name { text-decoration: line-through; }
ul.shoplist li.done .box { background: var(--accent); border-color: var(--accent); }

/* Lag mat. Read from a step back, with wet hands. */

h1.cook { font-size: 1.9rem; }
.card.today .dish { font-size: 1.35rem; font-weight: 700; margin: 0.2rem 0 0.8rem; }

ul.ingredients.cook li { font-size: 1.15rem; padding: 0.7rem 0; }
ul.ingredients.cook .amount { min-width: 5.5rem; }

.card.step { display: flex; gap: 0.9rem; align-items: flex-start; }
.card.step p { font-size: 1.15rem; line-height: 1.55; margin: 0; }
.stepno {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 700;
}

fieldset { border: 0; padding: 0; margin: 0.6rem 0; }
legend { font-weight: 600; padding: 0; margin-bottom: 0.2rem; }

.photos { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: 0.6rem; }
.photos figure { margin: 0; }
.photos img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius);
  background: var(--border);
}
.photos figcaption { text-align: center; }

input[type="file"] {
  width: 100%;
  min-height: var(--tap);
  padding: 0.6rem 0.75rem;
  font: inherit;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.steprow { display: flex; gap: 0.9rem; align-items: flex-start; margin-bottom: 0.9rem; }
.steprow p { margin: 0; }

.muted.warn { color: var(--danger); font-weight: 600; }

/* A code is read off one screen and typed into another, often without glasses. */
input.code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.4rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  text-align: center;
}

/* The queue. */
.card.queued .scaler { margin-top: 0.5rem; }
.queueactions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.9rem;
  margin-top: 0.6rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}
.queueactions form { display: inline; }
.queueactions .linky { padding: 0.5rem 0.2rem; margin: -0.5rem 0; }

/* Row actions under a catalogue entry. */
.rowactions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  padding: 0.1rem 1rem 0.6rem;
}
.rowactions form { display: inline; }
.rowactions .linky { padding: 0.5rem 0.2rem; margin: -0.5rem 0; }
ul.cards li { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
ul.cards a { border: 0; background: none; }

/* A queue entry with no recipe behind it is not a link. */
.cardtext {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.85rem 1rem;
}

/* Inventory: confidence is shown by weight, because it is a guess either way. */
ul.hensyn li .name { font-weight: 600; }
li.conf-usikker .name { font-weight: 400; }
li.conf-gammel { opacity: 0.55; }
li.conf-gammel .name { font-weight: 400; text-decoration: line-through; }

/* The assistant thread. */
.card.sagt { background: var(--bg); border-style: dashed; }
.card.sagt p { margin: 0; font-weight: 600; }
.card.svar p:last-child { margin-bottom: 0; }

/* What it did, above what it said: the actions are the part worth checking. */
ul.gjorde {
  list-style: none;
  margin: 0 0 0.9rem;
  padding: 0 0 0.7rem;
  border-bottom: 1px solid var(--border);
}
ul.gjorde li {
  padding: 0.2rem 0 0.2rem 1.4rem;
  font-size: 0.9rem;
  color: var(--muted);
  position: relative;
}
ul.gjorde li::before {
  content: '→';
  position: absolute;
  left: 0;
  color: var(--accent);
}

/* A command meant to be copied and run somewhere else. */
code.cmd {
  display: block;
  padding: 0.7rem 0.85rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.95rem;
  user-select: all;
  overflow-x: auto;
}

/* One person's verdict, in a box of its own so several fit on a phone. */
fieldset.rating {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
  margin: 0.7rem 0;
}
fieldset.rating legend { padding: 0 0.4rem; font-size: 0.9rem; }
fieldset.rating input[type="number"] { max-width: 6rem; }
