/* ============================================================
   PLATE — COMPONENT INVENTORY
   ============================================================ */

/* ---------- Buttons ---------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 8px 14px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 550;
  line-height: 1.2;
  white-space: nowrap;
  transition: background var(--t-fast) var(--e-out),
              border-color var(--t-fast) var(--e-out),
              transform var(--t-fast) var(--e-out),
              opacity var(--t-fast) linear;
}
.btn:hover { background: var(--surface-2); border-color: var(--line-strong); }
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: 0.45; pointer-events: none;
}
.btn svg { width: 15px; height: 15px; flex: 0 0 auto; }

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}
.btn--primary:hover { background: var(--accent-2); border-color: var(--accent-2); }

.btn--ghost { background: transparent; border-color: transparent; color: var(--ink-2); }
.btn--ghost:hover { background: var(--surface-2); color: var(--ink); border-color: transparent; }

.btn--danger { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 40%, var(--line)); }
.btn--danger:hover { background: var(--bad-soft); border-color: var(--bad); }

.btn--sm { padding: 5px 10px; font-size: var(--fs-xs); border-radius: var(--r-sm); }
.btn--sm svg { width: 13px; height: 13px; }
.btn--lg { padding: 11px 20px; font-size: var(--fs-md);   /* a primary call to action is a real 44px target, not a hit area */
  min-height: 46px;
}
.btn--block { width: 100%; }
.btn--icon { padding: 8px;   /* an icon-only control has no label to widen it, so it is sized */
  min-width: 44px; min-height: 44px;
}
.btn--icon.btn--sm { padding: 5px; }

.btnrow { display: flex; flex-wrap: wrap; gap: var(--s-2); }

/* ---------- Chips & pills ---------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: var(--r-full);
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: 1.5;
  white-space: nowrap;
}
/* ⭐ STATUS · THREE TREATMENTS, NOT SIX.

     state   a dot and a word          -- live: Open until 12am, Scheduled
     quiet   a fact about the thing    -- close-up, Mexican, a family tag
     loud    needs a decision          -- Needs a shot, Not sent

   Colour carries meaning only. Green, amber and red are reserved for state
   and are never used for emphasis; the accent stays the one action colour.

   `chip--mono` used to be uppercase mono -- the same shout as the old
   micro-label, on every card. It is now just quiet. */
.chip--mono {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  letter-spacing: normal;
  text-transform: none;
  background: transparent;
  border-color: var(--line);
  color: var(--ink-3);
}

/* state: the dot does the work, so the chrome can go */
.state {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--fs-sm); color: var(--ink-2);
}
.state::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--ink-4); flex: 0 0 auto;
}
.state--good::before { background: var(--good); }
.state--warn::before { background: var(--warn); }
.state--bad::before  { background: var(--bad); }
.state--live::before { background: var(--good); box-shadow: 0 0 0 3px var(--good-soft); }
.chip--good { color: var(--good); background: var(--good-soft); border-color: transparent; }
.chip--warn { color: var(--warn); background: var(--warn-soft); border-color: transparent; }
.chip--bad  { color: var(--bad);  background: var(--bad-soft);  border-color: transparent; }
.chip--info { color: var(--info); background: var(--info-soft); border-color: transparent; }
.chip--accent { color: var(--accent-on-soft); background: var(--accent-soft); border-color: var(--accent-line); }

/* Family chip: colour = information. */
.chip--fam {
  /* The family hue on its own 14% wash measured 4.11:1 — a fail on every
     idea card in the product. Mixing the hue toward the page ink lifts it
     over the line while keeping the family instantly recognisable, which
     is the entire job of this chip. */
  color: color-mix(in srgb, var(--fam) 72%, var(--ink));
  background: color-mix(in srgb, var(--fam) 14%, transparent);
  border-color: color-mix(in srgb, var(--fam) 32%, transparent);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.fam-dot {
  width: 7px; height: 7px; border-radius: 2px;
  background: var(--fam); flex: 0 0 auto;
}

/* Selectable chip (voice tags, goals, tags) */
.chip-select {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border-radius: var(--r-full);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  transition: all var(--t-fast) var(--e-out);
}
.chip-select:hover { border-color: var(--line-strong); color: var(--ink); }
.chip-select[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent-on-soft);
  font-weight: 550;
}
.chip-select[aria-pressed="true"]::before { content: "✓"; font-size: 10px; }

/* ---------- Cards ------------------------------------------ */
/* ⭐ SURFACES · EARN THE CONTAINER.

   A card came to mean "here is some content", so every screen became a
   field of identical boxes and nothing stood out. A card now means ONE
   thing: this is a distinct object you can act on -- a post idea, a photo,
   a promotion.

   Lists, forms, status lines and metrics get no container at all. They are
   separated by space and hairlines, which is what makes the cards that
   remain mean something.

   ⛔ And no shadow at rest. Shadow is for things that float -- modals,
   toasts, a dragged card. A shadow on a static panel is decoration
   pretending to be depth. */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}

/* The alternative to a card: a row in a list. Hairline, no box, no radius.
   This is what Menu's 71 dishes become. */
.rowlist { display: block; }
.rowlist > * + * { border-top: 1px solid var(--line); }
.row {
  display: flex; align-items: center; gap: var(--s-4);
  padding: var(--s-4) var(--s-2);
  transition: background var(--t-fast) var(--e-out);
}
.row:hover { background: var(--surface-2); }

/* A group of facts with no container -- the replacement for a grid of
   near-empty tiles. */
.facts { display: flex; flex-wrap: wrap; gap: var(--s-7) var(--s-9); }
.fact { display: grid; gap: 2px; }
.fact__n {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--fs-2xl); font-weight: 600; line-height: 1;
  letter-spacing: var(--tracking-tight);
}
.fact__l { font-size: var(--fs-sm); color: var(--ink-3); }
.card__hd {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--line);
}
.card__hd h3 { font-size: var(--fs-md); font-weight: 600; }
.card__bd { padding: var(--s-5); }
.card__ft {
  padding: var(--s-3) var(--s-5);
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/* ---------- THE TICKET — the signature object ---------------
   An idea is a kitchen order ticket coming off the rail:
   perforated top edge, mono ticket number, family colour on the
   left rail, editorial serif for the hook itself.
   ------------------------------------------------------------ */
.ticket {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--fam, var(--line-strong));
  border-radius: var(--r-xs) var(--r-xs) var(--r-md) var(--r-md);
  box-shadow: var(--shadow-1);
  transition: box-shadow var(--t-mid) var(--e-out),
              border-color var(--t-fast) var(--e-out),
              transform var(--t-mid) var(--e-out);
  overflow: hidden;
  text-align: left;
  width: 100%;
  display: block;
}
.ticket:hover {
  box-shadow: var(--shadow-2);
  transform: translateY(-1px);
  border-color: var(--line-strong);
  border-left-color: var(--fam, var(--line-strong));
}
/* perforation */
.ticket::before {
  content: "";
  position: absolute;
  top: -4px; left: 0; right: 0;
  height: 8px;
  background-image: radial-gradient(circle at 5px 4px, var(--ticket-notch) 3px, transparent 3.4px);
  background-size: 10px 8px;
  opacity: 0.9;
  pointer-events: none;
}
.ticket__hd {
  display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-4) var(--s-4) 0;
}
.ticket__no {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: 0.08em;
  color: var(--ink-4);
}
.ticket__hook {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  line-height: 1.3;
  letter-spacing: var(--tracking-tight);
  color: var(--ink);
  padding: var(--s-3) var(--s-4) 0;
}
.ticket__sub {
  padding: 6px var(--s-4) 0;
  font-size: var(--fs-sm);
  color: var(--ink-3);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.ticket__ft {
  display: flex; align-items: center; gap: var(--s-2);
  padding: var(--s-3) var(--s-4) var(--s-4);
  flex-wrap: wrap;
}
.ticket__thumb {
  width: 38px; height: 38px; border-radius: var(--r-sm);
  object-fit: cover; background: var(--surface-3);
  border: 1px solid var(--line);
  flex: 0 0 auto;
}
.ticket--posted { opacity: 0.62; }
.ticket--posted .ticket__hook { text-decoration: line-through; text-decoration-color: var(--ink-4); }

/* ---------- Meters ----------------------------------------- */
.meter { display: block; }
.meter__top {
  display: flex; align-items: baseline; gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.meter__val {
  font-family: var(--font-mono);
  font-size: var(--fs-2xl);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.meter__unit { font-size: var(--fs-xs); color: var(--ink-3); }
.meter__track {
  height: 6px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  overflow: hidden;
}
.meter__fill {
  height: 100%;
  border-radius: var(--r-full);
  background: var(--accent);
  transition: width var(--t-slow) var(--e-out);
}
.meter__fill--good { background: var(--good); }
.meter__fill--warn { background: var(--warn); }
.meter__fill--bad  { background: var(--bad); }
.meter__cap { font-size: var(--fs-xs); color: var(--ink-3); margin-top: var(--s-2); }

/* Segmented "fuel gauge" used by the freshness meter */
.segbar { display: flex; gap: 2px; height: 8px; }
.segbar i {
  flex: 1 1 0;
  border-radius: 1px;
  background: var(--surface-3);
  transition: background var(--t-mid) var(--e-out);
}
.segbar i[data-on="1"] { background: var(--accent); }

/* ---------- Stat tiles ------------------------------------- */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--s-3);
}
.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-4);
}
.stat__k { font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: var(--tracking-cap); text-transform: uppercase; color: var(--ink-3); }
.stat__v {
  font-family: var(--font-mono);
  font-size: var(--fs-2xl);
  font-weight: 600;
  line-height: 1.1;
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.stat__n { font-size: var(--fs-xs); color: var(--ink-3); margin-top: 3px; }

/* ---------- Form controls ---------------------------------- */
.field { display: block; margin-bottom: var(--s-4); }
.field__lb {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 550;
  color: var(--ink);
  margin-bottom: 5px;
}
.field__hint { font-size: var(--fs-xs); color: var(--ink-3); margin-bottom: 7px; line-height: 1.45; }
.field__err { font-size: var(--fs-xs); color: var(--bad); margin-top: 5px; }
/* "optional" is a reassurance, not a warning label. Sentence case, sans,
   and quiet -- the same discipline as the section labels. */
.field__opt { font-family: var(--font-ui); font-size: var(--fs-xs); color: var(--ink-quiet); text-transform: none; letter-spacing: normal; margin-left: 6px; font-weight: 400; }

.input, .textarea, .select {
  width: 100%;
  padding: 9px 12px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--ink);
  font-size: var(--fs-sm);
  transition: border-color var(--t-fast) var(--e-out), background var(--t-fast) var(--e-out), box-shadow var(--t-fast) var(--e-out);
}
.input::placeholder, .textarea::placeholder { color: var(--ink-4); }
.input:hover, .textarea:hover, .select:hover { border-color: var(--line-strong); }
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--accent-line);
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.textarea { resize: vertical; min-height: 84px; line-height: var(--lh-body); }
.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5' fill='none' stroke='%23808a93' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  padding-right: 30px;
}
.input--sm { padding: 6px 9px; font-size: var(--fs-xs); }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--s-4); }
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 var(--s-4); }
@media (max-width: 700px) { .grid2, .grid3 { grid-template-columns: 1fr; } }

/* Switch */
.switch { display: inline-flex; align-items: center; gap: var(--s-3); cursor: pointer; }
.switch__track {
  width: 38px; height: 22px; border-radius: var(--r-full);
  background: var(--surface-3);
  border: 1px solid var(--line);
  position: relative;
  transition: background var(--t-fast) var(--e-out), border-color var(--t-fast) var(--e-out);
  flex: 0 0 auto;
}
.switch__track::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--ink-3);
  transition: transform var(--t-mid) var(--e-out), background var(--t-fast) var(--e-out);
}
.switch[aria-checked="true"] .switch__track { background: var(--accent); border-color: var(--accent); }
.switch[aria-checked="true"] .switch__track::after { transform: translateX(16px); background: var(--accent-ink); }

/* Segmented control */
.seg {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.seg button {
  padding: 5px 12px;
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  font-weight: 550;
  color: var(--ink-3);
  transition: all var(--t-fast) var(--e-out);
}
/* ⭐ 29px tall by design -- a segmented control that stands 44px looks
   like two buttons stuck together. The TARGET is 44; the control is not.
   Same transparent-::after method the rest of the system uses. */
.seg button { position: relative; }
/* ⛔ -8px on a 28px control lands the target on 44.0 exactly, which rounds
   under. Clear it. */
.seg button::after { content: ""; position: absolute; inset: -10px -2px; }
.seg button:hover { color: var(--ink); }
.seg button[aria-pressed="true"] {
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-1);
}

/* Repeatable row (locations, deals, story beats) */
.rrow {
  display: flex; gap: var(--s-2); align-items: flex-start;
  margin-bottom: var(--s-2);
}
.rrow .input, .rrow .select { flex: 1 1 0; min-width: 0; }
.rrow__del {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  color: var(--ink-4);
  transition: all var(--t-fast) var(--e-out);
}
/* The remove button on a repeating row: 34px visual, 46px to the finger. */
.rrow__del { position: relative; }
.rrow__del::after { content: ''; position: absolute; inset: -6px; }
.rrow__del:hover { background: var(--bad-soft); color: var(--bad); }

/* ---------- Modal / sheet ---------------------------------- */
.modal-scrim {
  position: fixed; inset: 0;
  background: var(--scrim);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--s-5);
  animation: fade var(--t-fast) linear;
  overflow-y: auto;
}
.modal {
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-3);
  width: 100%;
  max-width: 560px;
  max-height: min(88vh, 900px);
  display: flex;
  flex-direction: column;
  animation: modalIn var(--t-mid) var(--e-out);
}
.modal--wide { max-width: 940px; }
.modal--tall { max-height: 92vh; }
@keyframes modalIn {
  from { opacity: 0; transform: translateY(12px) scale(0.985); }
  to { opacity: 1; transform: none; }
}
.modal__hd {
  display: flex; align-items: flex-start; gap: var(--s-3);
  padding: var(--s-5) var(--s-5) var(--s-4);
  border-bottom: 1px solid var(--line);
}
.modal__hd h2 {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  letter-spacing: var(--tracking-tight);
  line-height: 1.2;
}
.modal__hd p { font-size: var(--fs-sm); color: var(--ink-3); margin-top: 4px; }
.modal__x { margin-left: auto; flex: 0 0 auto; }
.modal__bd { padding: var(--s-5); overflow-y: auto; flex: 1 1 auto; }
.modal__ft {
  display: flex; gap: var(--s-2); justify-content: flex-end;
  padding: var(--s-4) var(--s-5);
  border-top: 1px solid var(--line);
  background: var(--surface-2);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  flex-wrap: wrap;
}
/* ⛔ A grid item is only as narrow as its min-content unless you say
   otherwise, so ONE unwrappable child -- the four platform tabs -- pushed
   this sheet to 422px inside a 390px phone, and the close x, the tabs, Copy
   and Close all fell off the right edge. Every level of the chain needs
   min-width:0 or the innermost one wins. */
.modal, .modal__bd, .modal__hd, .modal__ft { min-width: 0; }
.modal__bd > *, .modal__hd > * { min-width: 0; }

@media (max-width: 640px) {
  .modal-scrim { padding: 0; place-items: end stretch; }
  .modal {
    max-width: 100vw; width: 100%; max-height: 94vh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
  }
  .modal__ft { justify-content: stretch; }
  .modal__ft .btn { flex: 1 1 auto; }
  /* ⭐ The primary is the widest thing in the footer, on its own line, so
     the action the sheet exists for is never the one that wraps away. */
  .modal__ft .btn--primary { flex: 1 0 100%; order: -1; }
}

/* ⭐ A segmented control with four labels does not fit a phone. It scrolls
   inside its own box -- the same fix the filter strip got in v189 -- and
   the page still never scrolls sideways. */
@media (max-width: 640px) {
  .seg {
    max-width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .seg::-webkit-scrollbar { display: none; }
  .seg button { flex: 0 0 auto; white-space: nowrap; }
}

/* ---------- Toast ------------------------------------------ */
.toasts {
  position: fixed;
  bottom: var(--s-5); left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-toast);
  display: flex; flex-direction: column; gap: var(--s-2);
  align-items: center;
  pointer-events: none;
  width: min(440px, calc(100vw - 32px));
}
@media (max-width: 860px) { .toasts { bottom: calc(var(--tabbar-h) + var(--s-4)); } }
.toast {
  display: flex; align-items: center; gap: var(--s-3);
  background: var(--surface-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-3);
  padding: 10px var(--s-4);
  font-size: var(--fs-sm);
  color: var(--ink);
  pointer-events: auto;
  animation: toastIn var(--t-mid) var(--e-out);
  max-width: 100%;
}
.toast[data-tone="good"] { border-left: 3px solid var(--good); }
.toast[data-tone="bad"]  { border-left: 3px solid var(--bad); }
.toast[data-out="1"] { animation: toastOut var(--t-mid) var(--e-in-out) forwards; }
.toast__undo { margin-left: auto; color: var(--accent); font-weight: 600; font-size: var(--fs-xs); white-space: nowrap; }
@keyframes toastIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes toastOut { to { opacity: 0; transform: translateY(6px); } }

/* ---------- Empty / loading / error states ------------------ */
/* ⛔ The boxed, hatched, icon-topped empty state that used to live here was
   superseded by the quieter one further down this file (search `.empty__h`).
   Both definitions were live at once: the newer one won on width, alignment
   and padding, the older one still supplied the dashed box, the hatching and
   the centred text — so every empty screen in the app rendered as a hybrid of
   two designs that were never meant to meet. One design now, not one and a
   half. */

.skel {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 50%, var(--surface-2) 75%);
  background-size: 300% 100%;
  animation: shimmer 1.3s linear infinite;
  border-radius: var(--r-sm);
}
@keyframes shimmer { from { background-position: 150% 0 } to { background-position: -150% 0 } }

.spinner {
  width: 16px; height: 16px;
  border: 2px solid var(--line-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg) } }

.banner {
  display: flex; gap: var(--s-3); align-items: flex-start;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--ink-2);
}
.banner--warn { background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 30%, transparent); color: var(--ink); }
/* ⭐ The positive state. `chip--good` already existed; a banner that says
   something WORKED had no variant and rendered as a neutral grey box. */
.banner--good { background: var(--good-soft);
                border-color: color-mix(in srgb, var(--good) 30%, transparent);
                color: var(--ink); }
.banner--good svg { color: var(--good); }
.banner--bad  { background: var(--bad-soft);  border-color: color-mix(in srgb, var(--bad) 30%, transparent);  color: var(--ink); }
.banner--info { background: var(--info-soft); border-color: color-mix(in srgb, var(--info) 30%, transparent); color: var(--ink); }
.banner svg { flex: 0 0 auto; width: 17px; height: 17px; margin-top: 1px; }
.banner__act { margin-left: auto; flex: 0 0 auto; }

/* ============================================================
   ⭐ COLOUR IS NEVER THE ONLY SIGNAL

   `chip--good`, `chip--warn` and `chip--bad` carried their meaning in the
   text colour alone. To anyone with a red/green deficiency -- and in a
   greyscale screenshot -- a "3 photos" chip and a "Needs a shot" chip are
   the same object.

   Each semantic chip now leads with a small shape as well as a colour, and
   the shapes differ from each other: a filled dot for good, a hollow ring
   for something that wants attention, a filled square for a refusal. The
   colour still carries it for everyone who can see it; the shape carries it
   for everyone who cannot.
   ============================================================ */
.chip--good::before,
.chip--warn::before,
.chip--bad::before {
  content: '';
  width: 7px; height: 7px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: currentColor;
}
/* hollow ring — reads as "open", unresolved */
.chip--warn::before { background: transparent; box-shadow: inset 0 0 0 2px currentColor; }
/* square — reads as "stopped" */
.chip--bad::before { border-radius: 1px; }

/* ============================================================
   ⭐ TOUCH TARGETS  (design system · accessibility pass)

   Measured on the real app: 128 interactive controls below 44px --
   .chip at 25px (43 of them), .chip-select at 34, .btn--sm at 26,
   .navitem at 38. On a phone, where an owner fixes a price between
   services, a 25px chip is a mis-tap.

   ⛔ THE CONTROLS DO NOT GET BIGGER. An interface of 44px chips would be
   comically oversized and would undo the refinement this redesign is for.
   The HIT AREA grows instead, using a transparent ::after that extends
   past the visual bounds. The control looks identical and is twice as easy
   to hit -- which is exactly the technique both Apple and Material
   recommend for small glyphs.

   Two rules that keep it honest:
     · the expansion never exceeds half the gap to the next control, so
       neighbouring hit areas cannot overlap and steal each other's taps
     · it applies only where the element is genuinely interactive
   ============================================================ */
.chip:is(button, a, label),
.chip-select,
.btn,
.shotchip,
.goldmark,
.regpick,
.linkbtn,
.prof__navitem,
.setupcard__x,
.input--sm { position: relative; }

/* Expanded until the EFFECTIVE box clears 44 in both axes, measured rather
   than assumed. The visual size of every one of these is unchanged. */
.chip:is(button, a, label)::after,
.chip-select::after,
.shotchip::after {
  content: ''; position: absolute; inset: -11px; border-radius: inherit;
}
/* 34px controls need less; 22px inline links need more. */
.btn::after            { content: ''; position: absolute; inset: -6px; border-radius: inherit; }
.btn--sm::after        { inset: -10px; }
.btn--lg::after        { content: none; }
.linkbtn::after        { content: ''; position: absolute; inset: -12px -4px; }
.prof__navitem::after  { content: ''; position: absolute; inset: -6px; }
.setupcard__x::after   { content: ''; position: absolute; inset: -15px -8px; }
.input--sm::after      { content: ''; position: absolute; inset: -4px; }
/* Rows of chips need room for the expansion not to collide. 8px is the
   minimum spacing the guidance asks for between adjacent targets. */
.chiprow, .btnrow, .shotchips, .promorow__fx { gap: var(--s-2); }

/* Vertical nav has no neighbour-crowding problem, so it simply grows. */
.navitem { min-height: 44px; }

/* Inputs and selects are the one place people expect a comfortable target
   already; 40 → 44 costs nothing visually. */
.input, .select, .textarea { min-height: 44px; }
/* The compact input stays compact and gains its hit area instead. */
.input--sm, .select.input--sm { min-height: 36px; }

/* Icon-only buttons are the smallest things in the product and the easiest
   to miss. */
.btn--icon { position: relative; }
.btn--icon::after { content: ''; position: absolute; inset: -8px; }

/* ---------- Section headers --------------------------------
   ⭐ SPACING, IN THREE TIERS.

   Everything used to sit roughly --s-4 to --s-6 apart, so a label and the
   thing it labels were as far apart as two unrelated sections. Nothing
   grouped, and a page read as a list of parts rather than a composition.

     --s-3/4   inside an object   these belong together
     --s-6     between objects    these are siblings
     --s-9     between sections   these are different subjects

   A section header is the top of a subject, so it gets the section gap
   above it and the object gap below. */
.sechd {
  display: flex; align-items: center; gap: var(--s-3);
  margin-bottom: var(--s-4);
}
.sechd:not(:first-child) { margin-top: var(--s-9); }
.sechd h2 {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: 500;
  letter-spacing: var(--tracking-tight);
  line-height: 1.2;
}
.sechd p { font-size: var(--fs-sm); color: var(--ink-3); margin-top: 2px; }
.sechd__act { margin-left: auto; display: flex; gap: var(--s-2); align-items: center; }
.rule { height: 1px; background: var(--line); border: 0; margin: var(--s-6) 0; }

/* ---------- Tooltip ---------------------------------------- */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 7px); left: 50%;
  transform: translateX(-50%) translateY(3px);
  background: var(--surface-3);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow-2);
  padding: 4px 8px;
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  font-family: var(--font-ui);
  white-space: nowrap;
  opacity: 0; pointer-events: none;
  transition: opacity var(--t-fast) var(--e-out), transform var(--t-fast) var(--e-out);
  z-index: var(--z-sticky);
}
[data-tip]:hover::after { opacity: 1; transform: translateX(-50%); }

/* ---------- Copy-to-clipboard block ------------------------ */
.copyblock {
  position: relative;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s-4);
  font-size: var(--fs-sm);
  line-height: var(--lh-loose);
  white-space: pre-wrap;
  word-break: break-word;
}
.copyblock__btn {
  position: absolute; top: 7px; right: 7px;
  opacity: 0; transition: opacity var(--t-fast) linear;
}
.copyblock:hover .copyblock__btn, .copyblock:focus-within .copyblock__btn { opacity: 1; }

/* ---------- Progress rail (wizard) ------------------------- */
.prail { display: flex; align-items: center; gap: 6px; }
.prail__step {
  flex: 1 1 0;
  height: 3px;
  border-radius: var(--r-full);
  background: var(--surface-3);
  transition: background var(--t-mid) var(--e-out);
}
.prail__step[data-state="done"] { background: var(--good); }
.prail__step[data-state="now"]  { background: var(--accent); }

/* ---- written versions: every attempt is kept, none overwritten ---- */
.vers {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--s-2); margin: var(--s-3) 0 var(--s-1);
}
.vers__lb {
  font-family: var(--font-mono); font-size: var(--fs-2xs);
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3);
}
.vers__pill {
  min-width: 26px; height: 26px; padding: 0 6px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-strong);
  background: var(--surface-2); color: var(--ink-2);
  font-family: var(--font-mono); font-size: var(--fs-xs);
  cursor: pointer; transition: background .12s, color .12s, border-color .12s;
}
.vers__pill:hover { border-color: var(--accent-line); color: var(--ink); }
.vers__pill.is-on {
  background: var(--accent); border-color: transparent;
  color: var(--accent-ink); font-weight: 700;
}
.vers__hint { font-size: var(--fs-2xs); color: var(--ink-quiet); }

/* ---- note box: prove the typing landed, and scope it honestly ---- */
.fbk__state {
  display: flex; align-items: center; gap: 6px;
  min-height: 18px; margin-top: var(--s-2);
  font-size: var(--fs-xs); color: var(--ink-3);
}
.fbk__state.is-ok { color: var(--good); }
.fbk__state.is-pending { color: var(--ink-4); font-style: italic; }
.fbk__scope {
  margin-top: var(--s-3); padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}
.fbk__scopelb {
  display: block; margin-bottom: var(--s-2);
  font-size: var(--fs-xs); color: var(--ink-3);
}
.fbk__scopebtns { display: flex; flex-wrap: wrap; gap: var(--s-2); }
/* #45 · the default is a sentence now, not an inert button, so the label
   is prose and gets prose line-height. */
.fbk__scopelb { line-height: 1.5; }
.fbk__scopewhy {
  margin-top: var(--s-2);
  font-size: var(--fs-2xs); color: var(--ink-3); line-height: 1.45;
}

/* ---- what is permanently remembered about a dish/person/story ---- */
.subnote {
  margin-top: var(--s-3);
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  border-radius: var(--r-md);
  padding: var(--s-3) var(--s-4);
}
.subnote__hd {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-xs); color: var(--accent);
}
.subnote__hd svg { flex: none; }
.subnote__txt {
  margin: var(--s-2) 0 0; color: var(--ink);
  font-size: var(--fs-sm); line-height: 1.5; white-space: pre-wrap;
}
.subnote__ft {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); flex-wrap: wrap; margin-top: var(--s-3);
  font-size: var(--fs-2xs); color: var(--ink-3);
}

/* ---- "write more like this": the owner's own caption as the voice target ---- */
.btn.is-golden {
  background: var(--accent); color: var(--accent-ink);
  border-color: transparent; font-weight: 600;
}
.golden {
  margin-top: var(--s-3);
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  border-radius: var(--r-md);
  padding: var(--s-3) var(--s-4);
}
.golden__hd {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-xs); color: var(--accent);
}
.golden__hd svg { flex: none; }
.golden__txt {
  margin: var(--s-2) 0 0; color: var(--ink-2);
  font-size: var(--fs-sm); line-height: 1.5;
}

/* ---- "you can just edit this" — sits against the caption, not three
        sections below it where nobody read it ---- */
.caption__hint {
  display: flex; align-items: flex-start; gap: 6px;
  margin: var(--s-2) 0 0;
  font-size: var(--fs-xs); line-height: 1.5; color: var(--ink-3);
}
.caption__star {
  color: var(--accent); font-weight: 700; line-height: 1.2; flex: none;
}

/* A card still being written shows a neutral placeholder, not the angle
   template — see ui.js. Muted so it reads as a status, not a headline. */
.ticket__hook--pending { color: var(--ink-3); font-style: italic; }

/* Month picker inside the idea modal — see monthGrid() in ui.js. Shows what
   is already booked so a day is chosen with the week in view, not blind. */
.mgrid { margin-top: var(--s-3); padding: var(--s-3); border: 1px solid var(--line);
         border-radius: var(--r-md); background: var(--surface-2); }
.mgrid__hd { display: flex; align-items: center; justify-content: space-between;
             margin-bottom: var(--s-2); }
.mgrid__dow, .mgrid__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.mgrid__dow span { text-align: center; font-size: var(--fs-xs); color: var(--ink-3); }
.mgrid__day { position: relative; aspect-ratio: 1; border: 0; border-radius: var(--r-sm);
              background: transparent; color: var(--ink); cursor: pointer;
              font: inherit; font-size: var(--fs-sm); display: grid; place-items: center; }
.mgrid__day:hover:not(:disabled) { background: var(--surface-3); }
.mgrid__day[data-out="1"] { color: var(--ink-4); }
.mgrid__day:disabled { opacity: .32; cursor: default; }
.mgrid__day[data-today="1"] { outline: 1px solid var(--line-strong); }
.mgrid__day[data-sel="1"] { background: var(--accent); color: #fff; }
.mgrid__dots { position: absolute; bottom: 2px; left: 0; right: 0; text-align: center;
               font-size: 9px; line-height: 1; color: var(--accent); font-style: normal; }
.mgrid__day[data-sel="1"] .mgrid__dots { color: #fff; }

/* The browser draws its own calendar icon on a date input; ours sits beside
   the field and opens the month view, so suppress the native one rather than
   showing two icons that do different things. */
.input--date::-webkit-calendar-picker-indicator { display: none; -webkit-appearance: none; }
.input--date { appearance: none; }

/* What is already booked on the selected day. */
.mgrid__on { margin-top: var(--s-3); padding-top: var(--s-2); border-top: 1px solid var(--line); }
.mgrid__onrow { display: flex; gap: var(--s-2); align-items: baseline;
                font-size: var(--fs-sm); padding: 2px 0; }
.mgrid__onrow span:last-child { color: var(--ink-2); }
.mgrid__onrow { align-items: center; }
.mgrid__time { width: 7.5em; flex: 0 0 auto; }
.mgrid__onrow > span { flex: 1; }

/* ⭐ The day under the cursor has to be the one that looks active.
   The chosen day carried a solid accent fill while hover was a faint grey,
   so pointing at the 4th while the 5th stayed lit read as "these are the
   5th's posts". Hover now wins outright, and the chosen day steps back to an
   outline for as long as the grid is being pointed at. */
.mgrid__day:hover:not(:disabled) { background: var(--accent); color: #fff; }
.mgrid__day:hover:not(:disabled) .mgrid__dots { color: #fff; }
.mgrid__grid:hover .mgrid__day[data-sel="1"]:not(:hover) {
  background: transparent;
  color: var(--ink);
  box-shadow: inset 0 0 0 1.5px var(--accent);
}
.mgrid__grid:hover .mgrid__day[data-sel="1"]:not(:hover) .mgrid__dots { color: var(--accent); }

/* Day tooltips carry a date plus several post hooks, so the global
   nowrap tooltip ran straight off the panel. Wrap these ones only —
   every other tooltip in the app is a short label and should stay on
   one line. */
.mgrid__day[data-tip]::after {
  white-space: normal;
  width: max-content;
  max-width: 15rem;
  text-align: left;
  line-height: 1.45;
}
/* A centred popup on the first or last column overhangs the panel edge, so
   anchor those to the side they sit on. The transform is restated because it
   also carries the hover animation. */
.mgrid__grid .mgrid__day:nth-child(7n+1)[data-tip]::after {
  left: 0; transform: translateX(0) translateY(3px);
}
.mgrid__grid .mgrid__day:nth-child(7n+1)[data-tip]:hover::after { transform: translateX(0); }
.mgrid__grid .mgrid__day:nth-child(7n)[data-tip]::after {
  left: auto; right: 0; transform: translateX(0) translateY(3px);
}
.mgrid__grid .mgrid__day:nth-child(7n)[data-tip]:hover::after { transform: translateX(0); }

/* The photo on a scheduled day. A CSS tooltip can only draw text, so the
   images sit in the cell and are revealed on hover — which also means they
   are already loaded when they appear. */
.mgrid__peek {
  position: absolute; bottom: calc(100% + 7px); left: 50%;
  transform: translateX(-50%);
  display: none; gap: 3px; padding: 3px;
  background: var(--surface-3); border: 1px solid var(--line-strong);
  border-radius: var(--r-sm); box-shadow: var(--shadow-2);
  z-index: calc(var(--z-sticky) + 1);
}
.mgrid__day:hover .mgrid__peek { display: flex; }
.mgrid__peek img, .mgrid__nopic {
  width: 46px; height: 46px; object-fit: cover; border-radius: 3px;
  background: var(--surface-2); display: grid; place-items: center; color: var(--ink-3);
}
/* the text tooltip moves above the photos so the two do not overlap */
.mgrid__day[data-tip]:hover::after { bottom: calc(100% + 60px); }
.mgrid__grid .mgrid__day:nth-child(7n+1) .mgrid__peek { left: 0; transform: none; }
.mgrid__grid .mgrid__day:nth-child(7n) .mgrid__peek { left: auto; right: 0; transform: none; }

.mgrid__rowpic { width: 28px; height: 28px; object-fit: cover; border-radius: 3px;
                 flex: 0 0 auto; background: var(--surface-2); }

/* ---- The photos a scheduled post will carry, in posting order ----
   Left to right IS the carousel order, so they are laid out as a
   strip and never wrap: a photo that fell to a second line would
   read as "later" when it is not. A count takes over past three. */
.schedpics { display: inline-flex; align-items: center; gap: 3px; flex: 0 0 auto; }
.schedpics__im {
  width: 28px; height: 28px; object-fit: cover; border-radius: 3px;
  flex: 0 0 auto; background: var(--surface-2); border: 1px solid var(--line);
}
.schedpics__more {
  font-size: 11px; font-weight: 700; color: var(--ink-3);
  padding: 0 4px; flex: 0 0 auto;
}

/* ---- Where a post goes -----------------------------------------
   Deliberately quiet: it is context on a row, not the row's
   subject. The lead word ("Posted to" / "Goes to") carries whether
   this already happened, so it must never be dropped for space. */
.destline { display: inline-flex; gap: 4px; align-items: baseline;
            font-size: 12px; min-width: 0; }
.destline__lead { color: var(--ink-3); flex: 0 0 auto; }
.destline__nm   { color: var(--ink-2); font-weight: 600;
                  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.destline[data-dest-state="sent"] .destline__nm { color: var(--ink); }

.destlist { list-style: none; margin: var(--s-2) 0 0; padding: 0;
            display: flex; flex-direction: column; gap: var(--s-1); }
.destlist__it {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  padding: var(--s-2) var(--s-3); font-size: var(--fs-sm);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.destlist__it[data-off="1"] { opacity: .68; }
.destlist__nm   { font-weight: 600; }
.destlist__kind { color: var(--ink-2); }
.destlist__src  { margin-left: auto; color: var(--ink-3); font-size: 11px; }

/* ---- Publishing status (read-only, owner-facing) --------------
   A statement, not a control: no button affordance, no hover,
   nothing that invites a click it cannot honour. */
.pubstat {
  margin-top: var(--s-3); padding: var(--s-3);
  background: var(--surface-2); border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong); border-radius: var(--r-sm);
}
.pubstat--ok   { border-left-color: var(--good, var(--line-strong)); }
.pubstat--warn { border-left-color: var(--warn, var(--line-strong)); }
.pubstat--off  { border-left-color: var(--ink-3); }
.pubstat--wait { border-left-color: var(--line-strong); }
.pubstat__row { display: flex; align-items: baseline; gap: var(--s-2); }
.pubstat__lb  { color: var(--ink-3); font-size: var(--fs-sm); }
.pubstat__st  { font-weight: 700; }
.pubstat__dt  { margin-top: var(--s-2); font-size: var(--fs-sm); color: var(--ink-2);
                display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-2); }
.pubstat__lb2 { color: var(--ink-3); }
.pubstat__d   { font-weight: 600; color: var(--ink); }
.pubstat__d + .pubstat__d::before { content: ''; }
.pubstat__note { flex: 1 1 100%; color: var(--ink-3); font-size: 11px; }

.toast__choice {
  background: var(--surface-3); color: var(--ink); border: 1px solid var(--line-strong);
  border-radius: var(--r-full); padding: 3px 10px; font-size: 12px; font-weight: 600;
  cursor: pointer; white-space: nowrap;
}
.toast__choice:hover { background: var(--surface-2); border-color: var(--ink-3); }
/* A toast carrying choices needs room, or the question wraps to three
   lines and the buttons get squeezed. The container is 440px wide, so
   the toast has to break out of it rather than just raise its max. */
.toast[data-wide] {
  width: min(680px, calc(100vw - 32px));
  max-width: none;
  flex-wrap: wrap;
}
.toast[data-wide] > span { flex: 1 1 240px; }

/* ---- "What was wrong with it?" ---------------------------------
   Front and centre on purpose. Two quieter versions of this question
   (a strip above the plate, then buttons in the toast) were missed —
   "the question came down at the bottom. I almost missed it." Every
   option is one click, so blocking costs nothing but attention. */
.modal--why { max-width: 460px; }
.whyopts { display: flex; flex-direction: column; gap: var(--s-2); }
.whyopt {
  display: flex; flex-direction: column; gap: 2px; text-align: left;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface-2); color: var(--ink); cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.whyopt:hover { background: var(--surface-3); border-color: var(--line-strong); }
.whyopt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.whyopt__lb { font-weight: 600; font-size: var(--fs-sm); }
.whyopt__hint { color: var(--ink-3); font-size: var(--fs-xs); line-height: 1.35; }
.avoidlist { list-style: none; margin: var(--s-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-1); }
.avoidlist li {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-sm);
  font-size: var(--fs-sm);
}
.avoidlist li span { flex: 1; }

/* ---- Everything scheduled ------------------------------------
   A grid can only show one month. A list has no range, so a post
   can never be scheduled-but-invisible. */
.schedlist { display: flex; flex-direction: column; gap: var(--s-1); }
.schedrow {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  background: var(--surface-2); border: 1px solid var(--line);
  border-left: 3px solid var(--fam, var(--line-strong));
  border-radius: var(--r-sm); cursor: pointer; font-size: var(--fs-sm);
}
.schedrow:hover { background: var(--surface-3); }
.schedrow__when { min-width: 132px; color: var(--ink-2); }
.schedrow__time { min-width: 76px; color: var(--ink-3); }
.schedrow__hook { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 560px) {
  .schedrow { flex-wrap: wrap; }
  .schedrow__hook { flex: 1 1 100%; white-space: normal; }
}

/* ---- Carousel strip -------------------------------------------
   A post can carry several photos, and the ORDER is what gets
   posted — so it has to be visible, not implied. */
.photostrip { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-3); }
.photostrip__item { position: relative; width: 84px; }
.photostrip__item img {
  width: 84px; height: 84px; object-fit: cover;
  border-radius: var(--r-sm); border: 1px solid var(--line); display: block;
}
.photostrip__n {
  position: absolute; top: 4px; left: 4px; z-index: 1;
  background: rgba(0,0,0,.72); color: #fff;
  font-size: 11px; line-height: 1; padding: 3px 5px; border-radius: var(--r-xs);
}
.photostrip__x {
  position: absolute; top: 4px; right: 4px; z-index: 1;
  background: rgba(0,0,0,.72); color: #fff; border: 0; cursor: pointer;
  border-radius: var(--r-xs); padding: 3px; line-height: 0;
}
.photostrip__x:hover { background: var(--bad); }
/* ⭐ TOUCH TARGETS INSIDE A MODAL.

   The accessibility sweep walked eight screens and opened no modals, so the
   idea sheet -- the main creation surface in the product -- had never been
   measured. Eighteen of its controls stood 26px tall.

   Same method as everywhere else: the control keeps its visual size, a
   transparent ::after carries the target to 44. A sheet full of 44px-tall
   buttons would look like a form, and this is meant to read as a post. */
.modal .btn--sm,
.modal .chip,
.modal .seg button,
.modal .linkbtn,
.modal .capbox__bar .btn { position: relative; }
.modal .btn--sm::after,
.modal .chip:is(button, a, label)::after,
.modal .seg button::after,
.modal .linkbtn::after,
.modal .capbox__bar .btn::after {
  /* ⛔ -9px on a 26px control lands the target on 44.0 exactly, and 44.0
     rounds under. Same trap as `.drow__more`. Clear it, do not touch it. */
  content: ""; position: absolute; inset: -11px -4px;
}
/* Inputs are sized directly -- a date field you cannot hit is not a hit-area
   problem, it is a field that is too small. */
.modal .input--sm,
.modal input[type="date"],
.modal input[type="time"] { min-height: 44px; }
.modal .btn--icon { min-width: 44px; min-height: 44px; }
.modal__x { min-width: 44px; min-height: 44px; display: grid; place-items: center; }

/* The destructive end of a modal footer, held apart from the rest. */
.modal__ft-danger { margin-right: auto; }
@media (max-width: 560px) {
  .modal__ft-danger { margin-right: 0; width: 100%; order: 9; margin-top: var(--s-2); }
}
.pthumb[data-on="1"] { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- "Did this go out?" ---------------------------------------
   Past-dated posts Plate never sent. It genuinely cannot know, so
   it asks rather than guessing in either direction. */
.stalelist { display: flex; flex-direction: column; gap: var(--s-2); margin-top: var(--s-3); }
.stalerow {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  padding: var(--s-2) var(--s-3);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); font-size: var(--fs-sm);
}
.stalerow__when { min-width: 132px; color: var(--ink-2); }
.stalerow__hook { flex: 1; min-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* a date that has already gone, in the flat list */
.schedrow[data-past="1"] { opacity: .62; }
.schedrow[data-past="1"] .schedrow__when::after {
  content: ' · past'; color: var(--warn, var(--ink-3)); font-size: var(--fs-xs);
}

/* ---------- Dish photos, inside the dish form ----------------
   A strip of small squares plus one add button. Small on purpose:
   this is a confirmation that the right photo is attached, not a
   gallery — the gallery is the Photos screen. */
.dishpix{ display:flex; flex-wrap:wrap; gap:var(--s-2); margin-top:var(--s-2) }
.dishpix__i{
  position:relative; margin:0; width:76px; height:76px; border-radius:var(--r-md);
  overflow:hidden; background:var(--surface-2); border:1px solid var(--line);
}
.dishpix__i img{ width:100%; height:100%; object-fit:cover; display:block }
.dishpix__x{
  position:absolute; top:2px; right:2px; width:20px; height:20px; padding:0;
  display:grid; place-items:center; border:0; cursor:pointer;
  border-radius:999px; background:rgba(0,0,0,.62); color:#fff; line-height:0;
}
.dishpix__x svg{ width:12px; height:12px }
.dishpix__x:hover{ background:rgba(0,0,0,.82) }
.dishpix__add{
  width:76px; height:76px; border-radius:var(--r-md); cursor:pointer;
  border:1px dashed var(--line-strong); background:transparent; color:var(--ink-3);
  display:grid; place-items:center; gap:2px; font-size:11px; line-height:1.2;
  padding:4px; text-align:center;
}
.dishpix__add svg{ width:16px; height:16px }
.dishpix__add:hover{ border-color:var(--accent); color:var(--ink) }
.dishpix__x:focus-visible,.dishpix__add:focus-visible{ outline:2px solid var(--accent); outline-offset:2px }

/* ---------- TODAY — the command centre ----------------------
   Cards, not a feed. Everything here answers a question an owner
   actually has: is my site current, am I open, what's running, what's
   coming. Nothing here shows a caption. */
.today{ display:grid; gap:var(--s-6) }
.tsec{ display:grid; gap:var(--s-3) }

.alerts{ display:grid; gap:var(--s-2) }
.alert{
  display:flex; align-items:center; gap:var(--s-3);
  padding:var(--s-3) var(--s-4); border-radius:var(--r-md);
  border:1px solid var(--line); background:var(--surface-2);
}
.alert--warn{ border-color:color-mix(in srgb, var(--accent) 40%, var(--line)) }
.alert--bad { border-color:color-mix(in srgb, var(--bad) 45%, var(--line)) }
.alert__i{ display:grid; place-items:center; flex:none; color:var(--accent) }
.alert--bad .alert__i{ color:var(--bad) }
.alert__i svg{ width:18px; height:18px }
.alert__t{ flex:1; min-width:0 }
.alert .btn{ flex:none }

.tgrid{ display:grid; gap:var(--s-3); grid-template-columns:1fr }
@media (min-width:720px){ .tgrid{ grid-template-columns:1fr 1fr } }
@media (min-width:1180px){ .tgrid{ grid-template-columns:repeat(4,1fr) } }

.tcard{
  border:1px solid var(--line); border-radius:var(--r-md);
  background:var(--surface); display:flex; flex-direction:column; min-width:0;
}
.tcard__hd{
  padding:var(--s-3) var(--s-4); border-bottom:1px solid var(--line);
  font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--ink-3);
}
.tcard__bd{ padding:var(--s-4); display:grid; gap:var(--s-2); align-content:start; flex:1 }

.tstat{ display:flex; align-items:center; gap:var(--s-2); font-weight:500 }
.tstat .dot{ width:8px; height:8px; border-radius:50%; background:var(--ink-4); flex:none }
.tstat.is-live .dot{ background:#4CAF7D; box-shadow:0 0 0 3px color-mix(in srgb,#4CAF7D 22%,transparent) }
.tstat.is-stale .dot{ background:var(--accent); box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent) }
.tstat.is-shut .dot{ background:var(--ink-4) }

.tmuted{ color:var(--ink-3); font-size:14px; margin:0 }
.tpromo{ margin:0; font-weight:500 }
.tbig{ margin:0; font-size:28px; line-height:1.1; font-weight:600 }
.tbig span{ font-size:14px; font-weight:400; color:var(--ink-3) }
.trow{ display:flex; flex-wrap:wrap; gap:var(--s-2); margin-top:auto; padding-top:var(--s-2) }

.uplist{ display:grid; gap:1px; background:var(--line);
  border:1px solid var(--line); border-radius:var(--r-md); overflow:hidden }
.uprow{ display:grid; grid-template-columns:auto auto 1fr; gap:var(--s-3);
  align-items:baseline; padding:var(--s-3) var(--s-4); background:var(--surface) }
.uprow__d{ font-variant-numeric:tabular-nums; font-size:13px; color:var(--ink-2); white-space:nowrap }
.uprow__c{ font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-3) }
.uprow__t{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
@media (max-width:560px){
  .uprow{ grid-template-columns:1fr; gap:2px }
  .uprow__t{ white-space:normal }
}

.qacts{ display:grid; gap:var(--s-2); grid-template-columns:repeat(auto-fit,minmax(160px,1fr)) }
.qact{
  appearance:none; cursor:pointer; text-align:left;
  padding:var(--s-4); border-radius:var(--r-md);
  border:1px solid var(--line); background:var(--surface); color:var(--ink);
  font:inherit; font-weight:500; transition:border-color .18s, background .18s, transform .18s;
}
.qact:hover{ border-color:var(--accent); background:var(--surface-2) }
.qact:active{ transform:translateY(1px) }
.qact:focus-visible{ outline:2px solid var(--accent); outline-offset:2px }


/* Multi-paragraph confirmations (#72's replace warning is four points, and
   one wall of text hides the one that matters). */
.confirmmsg { display: grid; gap: var(--s-3); }
.confirmmsg p {
  font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.6; margin: 0;
}


/* ============================================================
   ⭐ PHOTOGRAPHY  (design system · phase 1)

   The product is about a restaurant and it was showing food at 40px. 62
   photos on file, none on Today, 72px grey camera squares repeated down
   Menu. This is the change that makes it look expensive, and it costs
   nothing because the photographs are already paid for.

   Photos render at OBJECT size. Three ratios, and no others:
     3:2   in a list, beside a name
     1:1   in a grid
     wide  full-bleed at the top of a screen

   ⛔ A missing photo is never a grey camera icon. It is the dish's own name
   set in the serif on a flat tone -- which reads as considered rather than
   broken, and stops 41 identical placeholders becoming the visual rhythm of
   a screen.
   ============================================================ */
.ph {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--surface-2);
  flex: 0 0 auto;
}
.ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ph--32  { aspect-ratio: 3 / 2; }
.ph--sq  { aspect-ratio: 1 / 1; }
.ph--wide { aspect-ratio: 21 / 9; border-radius: var(--r-lg); }

/* list-beside-a-name: big enough to see the food, small enough to scan */
.ph--row { width: 108px; aspect-ratio: 3 / 2; }
@media (max-width: 720px) { .ph--row { width: 84px; } }

/* the typographic placeholder */
/* ⭐ THE FAILED-IMAGE STATE, FOR EVERY `.ph` IN THE PRODUCT.

   `media.hydrate()` marks the frame `is-missing` when a record's pixels
   will not load. Three surfaces styled that individually -- the photo grid,
   the idea sheet, the hero -- and every OTHER `.ph` still fell through to
   the browser's broken-image glyph with the alt text spilling across it,
   which is precisely the thing the intentional placeholder exists to
   replace. One rule, on the shared class, so a new surface inherits it. */
.ph.is-missing img { display: none; }
.ph.is-missing {
  display: grid; place-items: center;
  background: var(--surface-2);
  border: 1px dashed var(--line);
}
.ph.is-missing::after {
  content: "photo unavailable";
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  color: var(--ink-4);
  text-align: center;
  padding: var(--s-2);
}

.ph--none {
  display: grid; place-items: center;
  padding: var(--s-3);
  background: var(--surface-2);
  border: 1px dashed var(--line);
}
.ph--none small {
  /* ⛔ Inline by default, so this ran straight on from the name above it.
     It is a caption on its own line, and always was. */
  display: block;
  margin-top: 3px;
  font-size: var(--fs-2xs);
  color: var(--ink-4);
}
.ph--none span {
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  color: var(--ink-4);
  text-align: center;
  line-height: 1.25;
  overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
}

/* a caption sitting over an image needs a floor to stand on, not a
   decorative gradient — this one exists to make text legible */
.ph__over {
  position: absolute; inset: auto 0 0 0;
  padding: var(--s-5);
  background: linear-gradient(to top, rgba(0,0,0,0.78), rgba(0,0,0,0.35) 55%, transparent);
  color: #fff;
}

/* ============================================================
   ⭐ BUTTON HIERARCHY · ONE PRIMARY PER SCREEN

   Not a new component -- the button styles are already right. What was
   wrong is how many primaries a screen had: Menu's header carried three
   filled buttons of equal weight, and Today carried none.

   The rule is enforced by review, not by CSS, but this makes the
   secondary genuinely secondary so the primary can be alone.
   ============================================================ */
.btn--secondary {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--ink);
}
.btn--secondary:hover { background: var(--surface-2); }

/* ============================================================
   MOTION · confirms an action, never announces the app
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ============================================================
   ⭐ EMPTY STATES  (design system · phase 1)

   An empty state used to be a grey banner inside a card inside a section --
   three containers to say "nothing yet". It looked like an error and it
   looked exactly like a filled state, so a screen with nothing on it read
   as a screen that had failed to load.

   An empty state is a COMPOSITION, not a notice: a line in the display
   serif saying what will be here, one sentence of why it is worth doing,
   and the single action that starts it. No box, no icon, no banner.
   ============================================================ */
.empty {
  display: grid; gap: var(--s-3);
  justify-items: start;
  padding: var(--s-8) 0 var(--s-7);
  max-width: 46ch;
}
.empty--center { justify-items: center; text-align: center; margin: 0 auto; }
.empty__h {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: 500;
  line-height: var(--lh-snug);
  color: var(--ink);
}
.empty__p { font-size: var(--fs-sm); color: var(--ink-3); line-height: var(--lh-body); }
.empty .btn { margin-top: var(--s-2); }


/* ---- Customers (read-only) -------------------------------------
   Every row answers two questions and no more: can this person be
   emailed, can they be texted, and if not — why. The reason is the
   point; a bare "No" sends somebody to support. */
.cstat { display: flex; flex-wrap: wrap; gap: var(--s-5); }
.cstat__i { display: flex; flex-direction: column; gap: 2px; }
.cstat__i b { font-size: var(--fs-xl, 22px); line-height: 1; }
.cstat__i span { font-size: var(--fs-sm); color: var(--ink-2); }

.cseg { display: flex; flex-wrap: wrap; gap: var(--s-1); margin-top: var(--s-4); }
.cseg__b {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; font-size: var(--fs-sm); font-weight: 600;
  background: var(--surface-2); color: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--r-full); cursor: pointer;
}
.cseg__b:hover { background: var(--surface-3); }
/* ⛔ `--surface-1` HAS NEVER EXISTED. The token is `--surface`, so this
   resolved to nothing and the active tab rendered light-on-light — its
   label invisible on every screen using a segmented control. Found by
   screenshotting the rendered page rather than by reading the file, which
   is the only way a colour that resolves to "unset" ever shows up. */
.cseg__b[aria-pressed="true"] { background: var(--ink); color: var(--surface); border-color: var(--ink); }
.cseg__n { opacity: .7; font-weight: 700; }

.clist { display: flex; flex-direction: column; gap: var(--s-1); margin-top: var(--s-2); }
.crow {
  display: grid; grid-template-columns: 1fr auto auto; gap: var(--s-3);
  align-items: center; padding: var(--s-3);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-sm);
}
.crow__nm { font-weight: 600; }
.crow__ct { display: flex; flex-wrap: wrap; gap: var(--s-3);
            font-size: var(--fs-sm); color: var(--ink-2); margin-top: 2px; }
.crow__tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.crow__ch { display: flex; flex-direction: column; gap: 4px; }
.crow__meta { display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
              font-size: 11px; color: var(--ink-3); text-align: right; }
.crow__b { display: inline-flex; align-items: center; gap: 4px; color: var(--ink-2); }

/* Yes/no carries a colour AND the word, never colour alone. */
.cchip { display: inline-flex; align-items: baseline; gap: 6px;
         font-size: 11px; white-space: nowrap; }
.cchip__k { color: var(--ink-3); min-width: 34px; }
.cchip__v { font-weight: 600; }
.cchip--yes .cchip__v { color: var(--good); }
.cchip--no  .cchip__v { color: var(--ink-2); }

@media (max-width: 640px) {
  .crow { grid-template-columns: 1fr; }
  .crow__ch { flex-direction: row; flex-wrap: wrap; gap: var(--s-3); }
  .crow__meta { align-items: flex-start; text-align: left; }
}

/* A raw string from a provider — an error code, an exported payload. It is
   not prose and must not be set in the UI face: monospace is what tells the
   reader "this is verbatim, copy it exactly". */
.mono { font-family: var(--font-mono); font-size: var(--fs-xs);
        overflow-wrap: anywhere; }
.pre {
  font-family: var(--font-mono); font-size: var(--fs-xs);
  line-height: 1.5; margin: 0;
  padding: var(--s-3);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-md);
  /* ⛔ A dump of unknown width. Without this it pushes the whole screen
     sideways instead of scrolling inside its own box. */
  overflow-x: auto; max-height: 340px; overflow-y: auto;
}

/* ---------- Add a restaurant: the account choice ---------- */
fieldset[data-newrest-pick] { border: 0; padding: 0; margin: 0; }
.radio { display: flex; align-items: center; gap: 8px; padding: 6px 0; cursor: pointer; }
.radio input { accent-color: var(--accent, currentColor); }
[data-newrest-newbox] { padding: 4px 0 0 26px; }
[data-newrest-status][data-tone="bad"] { color: var(--bad); }

/* ---------- Delete a restaurant: what goes with it ---------- */
.delrest__list { margin: 6px 0 10px; padding-left: 18px; }
.delrest__list li { padding: 2px 0; }
[data-delrest-status][data-tone="bad"] { color: var(--bad); }

/* ---------- Restaurants grouped by account ---------- */
.acct { margin-bottom: var(--s-6, 28px); }
.acct__hd { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin-bottom: 10px; flex-wrap: wrap; }
.acct__nm { margin: 0; font-size: var(--fs-lg, 1.05rem); }
.acct__meta { color: var(--ink-2, inherit); font-size: var(--fs-sm, .85rem); }
.acct__acts { display: flex; gap: 6px; flex-wrap: wrap; }
.acct__empty { color: var(--ink-2, inherit); font-size: var(--fs-sm, .85rem); padding: 14px 16px; border: 1px dashed var(--line, currentColor); border-radius: var(--r-md, 10px); margin: 0; }
[data-acct-status][data-tone="bad"], [data-acctdel-status][data-tone="bad"] { color: var(--bad); }

/* ---------- People ---------- */
.people__list { list-style: none; margin: 0 0 10px; padding: 0; }
.people__item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--line, rgba(127,127,127,.2)); flex-wrap: wrap; }
.people__email { font-weight: 600; word-break: break-all; }
.people__acts { display: flex; gap: 6px; align-items: center; }
.people__invite { margin-top: 14px; }
.people__row { display: flex; gap: 6px; flex-wrap: wrap; }
.people__row .input { flex: 1 1 200px; }
[data-people-status][data-tone="bad"] { color: var(--bad); }
.people__who { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-width: 0; }
