/* public/css/app.css — this app's OWN design language. Neither of the
   hotel's website systems applies here (agent-memory/inspect-app-design-
   language.md, operator ruling 2026-08-18): Uber-style high-contrast
   clarity, grayscale surfaces, functional color only for severity. */
:root {
  color-scheme: light;
  --ink: #141414;      /* text + primary-button fill */
  --muted: #5E5E5E;    /* secondary text, >=4.5:1 on white */
  --line: #E2E2E2;     /* hairlines */
  --fill: #F6F6F6;     /* subtle fills, neutral card */
  --bg: #FFFFFF;
  --h: #B3261E;         /* high priority — red */
  --h-bg: #FBEAE9;
  --m: #B26A00;         /* medium priority — amber, dark enough for white text */
  --m-bg: #FCF1DE;
  --l: #1D4ED8;         /* low priority — blue */
  --l-bg: #E9EFFD;
  --radius: 8px;
  --bar-h: 64px;
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 15px;
}
#app { display: flex; flex-direction: column; min-height: 100vh; }
button { font-family: inherit; -webkit-tap-highlight-color: transparent; }
[hidden] { display: none !important; }

/* ---------- sign-in ---------- */
.signin {
  max-width: 320px; margin: 18vh auto 0; padding: 0 20px;
  display: flex; flex-direction: column; gap: 12px;
}
.signin h1 { font-size: 28px; font-weight: 800; margin: 0 0 8px; }
.signin label {
  font-size: 12px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.signin input {
  min-height: 44px; padding: 0 12px; font-size: 16px;
  border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink);
}
.signin button {
  min-height: 44px; border: none; border-radius: var(--radius);
  background: var(--ink); color: #fff; font-weight: 700; font-size: 16px;
}
.error { color: var(--h); font-size: 13px; font-weight: 600; margin: 0; }

/* ---------- the shell: scrolling screen + floating bar ---------- */
.screen { flex: 1; overflow-y: auto; padding-bottom: calc(var(--bar-h) + 24px); }

.bar {
  position: fixed; left: 0; right: 0; bottom: 0;
  display: flex; justify-content: space-around; align-items: center;
  height: var(--bar-h);
  padding-bottom: env(safe-area-inset-bottom, 0px);
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
  border-top: 1px solid var(--line);
}
.bar__btn {
  position: relative;
  min-width: 44px; min-height: 44px; border: none; border-radius: var(--radius);
  background: none; color: var(--muted); font-size: 22px;
  display: flex; align-items: center; justify-content: center;
}
.bar__btn.is-on { color: var(--ink); background: var(--fill); }

/* Task 12 fix round 1, I1 — the outbox badge on the '+' button. Plain
   ("pending-count") is neutral: normal, self-healing, nothing to do.
   The error variant ("queue-failed") is red on purpose — it only ever
   shows for a permanently-stuck entry or an already-dropped photo, i.e.
   something the automatic drain() has already given up on. */
[data-testid="pending-count"], [data-testid="queue-failed"] {
  position: absolute; top: -4px; right: -4px;
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center;
  background: var(--ink); color: #fff;
}
[data-testid="queue-failed"] { background: var(--h); }

/* ---------- Waiting — Task 12 fix round 1, I1/I2 ---------- */
.waiting { max-width: 480px; margin: 0 auto; padding: 20px; }
.waiting h2 { font-size: 20px; margin: 0 0 12px; }
.waiting__empty { color: var(--muted); }
.waiting__row {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 12px; margin-bottom: 10px;
}
.waiting__row--error { border-color: var(--h); background: var(--h-bg); }
.waiting__rooms { font-weight: 700; margin: 0; }
.waiting__detail { margin: 2px 0; color: var(--ink); }
.waiting__meta { margin: 0; font-size: 12px; color: var(--muted); }
.waiting__actions { display: flex; gap: 8px; margin-top: 8px; }
.waiting__actions button {
  min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--bg); color: var(--ink);
  font-weight: 700; font-size: 13px;
}

/* ---------- the room grid ---------- */
.grid {
  display: grid; grid-template-columns: repeat(6, 1fr);
  gap: 6px; padding: 16px;
}
.room {
  position: relative; aspect-ratio: 1; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--fill); color: var(--ink);
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 13px; padding: 0;
}
.room--h { background: var(--h-bg); border-color: var(--h); }
.room--m { background: var(--m-bg); border-color: var(--m); }
.room--l { background: var(--l-bg); border-color: var(--l); }
.room__count {
  position: absolute; top: -6px; right: -6px;
  min-width: 18px; height: 18px; padding: 0 4px; border-radius: 999px;
  background: var(--ink); color: #fff; font-size: 11px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}

/* ---------- table: filter, sort, select, close many at once ---------- */
.table { padding: 16px; color: var(--muted); }
.table table { width: 100%; border-collapse: collapse; font-size: 13px; }
.table th, .table td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--line); }
.p--H { color: var(--h); font-weight: 700; }
.p--M { color: var(--m); font-weight: 700; }
.p--L { color: var(--l); font-weight: 700; }

.filters {
  display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px;
}
.filters select, .filters input[type="search"] {
  min-height: 40px; padding: 6px 10px; font-size: 14px;
  border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink);
  font-family: inherit; background: var(--bg);
}
.filters input[type="search"] { flex: 1; min-width: 140px; }

.bulk {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; margin-bottom: 8px;
  background: var(--fill); border: 1px solid var(--line); border-radius: var(--radius);
  font-size: 13px; font-weight: 600; color: var(--ink);
}
.bulk button {
  min-height: 36px; padding: 0 12px; border: none; border-radius: var(--radius);
  background: var(--ink); color: #fff; font-weight: 700; font-size: 13px;
}
.bulk button:disabled { background: var(--line); color: var(--muted); }

.merge {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line);
}
.merge__label {
  font-size: 12px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.06em; width: 100%;
}
.merge select {
  min-height: 40px; padding: 6px 10px; font-size: 14px;
  border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink);
  font-family: inherit; background: var(--bg);
}
.merge button {
  min-height: 40px; padding: 0 12px; border: none; border-radius: var(--radius);
  background: var(--ink); color: #fff; font-weight: 700; font-size: 13px;
}
.merge button:disabled { background: var(--line); color: var(--muted); }

/* ---------- the create form: one typed entry, many rooms ---------- */
.create {
  max-width: 480px; margin: 0 auto; padding: 20px;
  display: flex; flex-direction: column; gap: 8px;
}
.create h2 { font-size: 20px; margin: 0 0 8px; }
.create label {
  font-size: 12px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.06em; margin-top: 8px;
}
.create select, .create textarea, .create input[type="date"] {
  min-height: 44px; padding: 8px 12px; font-size: 16px;
  border: 1px solid var(--line); border-radius: var(--radius); color: var(--ink);
  font-family: inherit; width: 100%;
}
.create textarea { min-height: 72px; }
.create label.ooo {
  display: flex; align-items: center; gap: 8px;
  text-transform: none; font-size: 15px; font-weight: 500; color: var(--ink);
}
.create button[type="submit"] {
  min-height: 44px; margin-top: 12px; border: none; border-radius: var(--radius);
  background: var(--ink); color: #fff; font-weight: 700; font-size: 16px;
}

.seg { display: flex; gap: 8px; }
.seg label {
  display: flex; align-items: center; gap: 4px;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 8px 12px; text-transform: none; font-size: 14px; color: var(--ink);
}

.roompicker { position: relative; }
.roompicker input[type="search"] {
  min-height: 44px; padding: 8px 12px; font-size: 16px;
  border: 1px solid var(--line); border-radius: var(--radius); width: 100%;
}
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.chip {
  border: 1px solid var(--line); border-radius: 999px; background: var(--fill);
  padding: 6px 10px; font-size: 13px; font-weight: 600; color: var(--ink);
}
.options {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 10;
  margin: 0; padding: 4px; list-style: none;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg);
  max-height: 240px; overflow-y: auto;
}
.options li { padding: 10px 12px; font-size: 15px; border-radius: 6px; }
.options li:hover, .options li[aria-selected="true"] { background: var(--fill); }
.options__all { font-weight: 700; border-bottom: 1px solid var(--line); }

.photo-thumbs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
.photo-thumb { position: relative; width: 64px; height: 64px; }
.photo-thumb img {
  width: 100%; height: 100%; object-fit: cover;
  border-radius: var(--radius); border: 1px solid var(--line);
}
.photo-thumb button {
  position: absolute; top: -6px; right: -6px; width: 22px; height: 22px;
  border-radius: 999px; border: 1px solid var(--line); background: var(--bg);
  color: var(--ink); font-size: 14px; line-height: 1; padding: 0;
}

/* ---------- Fix round 1, IMPORTANT #8: voice — the primary new control on
   this form had no rule anywhere in this file, so it rendered as a default
   browser button: small, inline, no guaranteed 44px tap target, and no
   visual weight above the Code field it sits over. .is-recording was a
   no-op class — text was the only feedback that recording had started. ---- */
.voice-btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 52px; width: 100%; margin: 4px 0 4px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--fill); color: var(--ink);
  font-weight: 700; font-size: 15px;
  /* touch-action: none stops iOS's own press-and-hold text-selection /
     callout from competing with the hold-to-speak gesture this button IS. */
  touch-action: none; -webkit-user-select: none; user-select: none;
}
.voice-btn:disabled { opacity: 0.5; }
/* A plain dot rather than an icon font — this app ships no icon set (see
   the grayscale-surfaces note at the top of this file) — filled red only
   while actually recording, so the one state that matters (is the mic hot
   right now) is visible at a glance, not just readable in the button text. */
.voice-btn::before {
  content: ''; width: 10px; height: 10px; border-radius: 999px;
  background: var(--muted); flex-shrink: 0;
}
.voice-btn.is-recording {
  background: var(--h-bg); border-color: var(--h); color: var(--h);
}
.voice-btn.is-recording::before { background: var(--h); }

/* ---- 2026-09-21. The operator, after his first real walk: "after I am done
   speaking, I don't know what is happening cause there is no spinning
   buffering animation to tell me that it is processing."
   He was right, and the old behaviour was worse than a missing spinner: on
   release the label snapped back to "Hold to speak" and the button went
   disabled, so for 3-12 seconds he was looking at a greyed-out control
   telling him to hold it.
   The same dot that reports "the mic is hot" now reports "the server is
   working" — one element, two states, no new markup and no icon set (this
   app ships none, see the grayscale note at the top of this file). ---- */
.voice-btn.is-busy {
  /* Cancels the :disabled dimming above ON PURPOSE. It is disabled because a
     second press would do nothing useful, NOT because it is inert — and a
     greyed-out spinner reads as broken, which is the exact impression this
     change exists to remove. */
  opacity: 1;
}
.voice-btn.is-busy::before {
  width: 12px; height: 12px;
  background: transparent;
  border: 2px solid var(--line);
  border-top-color: var(--ink);
  animation: voice-spin 0.7s linear infinite;
}
@keyframes voice-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  /* Still moving, because "is it working?" is the entire question being
     answered — but a pulse rather than a spin. */
  .voice-btn.is-busy::before {
    border-color: transparent; background: var(--ink);
    animation: voice-pulse 1.4s ease-in-out infinite;
  }
  @keyframes voice-pulse { 50% { opacity: 0.2; } }
}

/* Task 13's NEW badge (a voice result proposing a genuinely new code) used
   to be bare inline text next to the "or type a new code" input — same
   pill treatment as .chip, so it reads as a status marker rather than part
   of the sentence around it. */
.badge {
  /* align-self, not just display:inline-flex — this badge is a DIRECT CHILD
     of .create, a flex COLUMN with the default align-items: stretch, so
     without this override it stretched edge to edge (caught by actually
     looking at it in the browser, not by reading the rule). */
  display: inline-flex; align-items: center; align-self: flex-start;
  margin: 2px 0 0; border-radius: 999px; background: var(--ink); color: #fff;
  padding: 2px 8px; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em;
}

/* ---------- Task 14 — the send panel: out of order → one WhatsApp message.
   The single outward-facing action in the whole app (a message that reaches
   another human being), so its primary CTA (.btn) gets the same filled,
   high-contrast treatment as Save and Sign in — not a default blue link. ---- */
.sendpanel { max-width: 480px; margin: 0 auto; padding: 20px; }
.sendpanel h2 { font-size: 20px; margin: 0 0 12px; }
.sendpanel p { color: var(--muted); font-size: 14px; line-height: 1.4; }
.sendpanel pre {
  white-space: pre-wrap; word-break: break-word; font-family: inherit;
  font-size: 15px; line-height: 1.4; margin: 0 0 16px; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--fill); padding: 12px;
}
.sendpanel__actions { display: flex; flex-direction: column; gap: 8px; }
.sendpanel button {
  width: 100%; min-height: 44px; margin-bottom: 8px; padding: 0 12px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); color: var(--ink); font-weight: 700; font-size: 14px;
}
.btn {
  display: flex; align-items: center; justify-content: center;
  min-height: 44px; margin-bottom: 8px; border-radius: var(--radius);
  background: var(--ink); color: #fff; font-weight: 700; font-size: 16px;
  text-decoration: none;
}

/* ---------- Task 14 — the nudge list (table.js top, spec §6.3): batches
   out of order, expiring within a day, work still open. Amber (--m), same
   family as a medium-priority room card — due soon, not yet a crisis. ---- */
.nudges { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.nudge {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1px solid var(--m); background: var(--m-bg); border-radius: var(--radius);
  padding: 10px 12px;
}
.nudge__rooms { font-weight: 700; margin: 0; color: var(--ink); }
.nudge__until { margin: 2px 0 0; font-size: 12px; color: var(--muted); }
.nudge__actions { flex-shrink: 0; }
.nudge__actions a, .nudge__actions button {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 36px; padding: 0 12px; margin: 0; border: none;
  border-radius: var(--radius); background: var(--ink); color: #fff;
  font-weight: 700; font-size: 13px; text-decoration: none;
}
