/* The QR page, dressed as the customer app.
   ============================================================================

   This page used to wear the *vendor* app's palette — the warm chalkboard of
   `apps/vendor_app/lib/theme.dart`, with turmeric and tandoor teal — and said
   so in this comment. That was wrong about who is reading it. A vendor sees
   the chalkboard because they are working behind a counter all evening; the
   person who scans this code is a customer standing in front of one, and two
   minutes later they may open the customer app and see the same stall. Those
   two surfaces answering the same question in two different visual languages
   is the seam, not the consistency.

   So the tokens below are the customer app's counter palette
   (`apps/customer_app/lib/theme.dart`, BoboQCounter): a dark neutral board,
   one lit green reserved for live state, and white turned down twice for
   everything read rather than glanced at. Keep them in step with that file.

   **On the display face.** The app sets every figure in Exo 2 italic — the
   rule there is "the face is the app's own voice, it is never its labelling".
   This page does not load it. A webfont is a blocking download on exactly the
   weak connection the QR page spec exists to be fast on, and it would be the
   only asset request this page makes. Figures here are set italic, heavy and
   tabular in the system face instead: the same gesture, none of the bytes.
   That is a deliberate approximation, not an oversight. */

:root {
  /* The board's unlit ground, and the raised surfaces on it. */
  --ground: #212121;
  --board: #2e2e2e;

  /* Lit. Live state, and nothing else — an open stall, a ready token, the
     control that takes your place in the line. Green that covers a screen
     stops meaning live. */
  --lit: #5dd62c;
  --unlit: #337418;

  --text: #f8f8f8;
  /* Small print: the same white, turned down. The palette has no mid grey,
     and inventing one would put another colour on the screen. */
  --quiet: rgba(248, 248, 248, 0.55);
  --fainter: rgba(248, 248, 248, 0.45);
  --edge: rgba(248, 248, 248, 0.14);
  --rim: rgba(248, 248, 248, 0.24);

  /* The one alarming colour, for a refusal. Lifted off a print red, which is
     unreadable on a near-black screen. */
  --danger: #ff6b5a;

  /* The FSSAI diet marks, carried over unchanged: these are recognised
     before they are read, so they are not ours to restyle. */
  --veg: #0a8020;
  --egg: #e9a400;
  --non-veg: #d93a1f;

  /* One place for the prep tile's size, because the dashed rule between
     dishes has to start where the tile ends. */
  --tile: 3.375rem;
  --tile-gap: 0.875rem;
}

[hidden] {
  display: none !important;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--text);
  background: var(--ground);
  min-height: 100vh;
  line-height: 1.4;
}

#app {
  max-width: 480px;
  margin: 0 auto;
  padding: 1.25rem 1rem 3rem;
  /* Tall enough to reach the bottom of the window even when a stall sells
     three dishes, so the footer below can be pushed down to meet it rather
     than floating in the middle of an empty screen. box-sizing accounts for
     the padding above, which would otherwise push the page into a scroll
     that has nothing in it. */
  box-sizing: border-box;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* The one view long enough to need this. It stretches to whatever #app has,
   so `margin-top: auto` on the footer inside it has room to work. */
#vendor-view {
  flex: 1;
  display: flex;
  flex-direction: column;
}

a,
button,
input,
[tabindex] {
  outline-offset: 2px;
}
a:focus-visible,
button:focus-visible,
input:focus-visible {
  outline: 2px solid var(--lit);
}

/* --- Stall header ------------------------------------------------------ */

.stall-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1.5rem;
}

.stall-header h1 {
  font-size: 1.4rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  /* Set as a name, not as a label. It was uppercased, which on a stall called
     "SEC-18 Chaap" turned a proper noun into signage — the app prints the
     vendor's own capitalisation and so does this. */
}

.pill {
  display: inline-block;
  padding: 0.25rem 0.65rem;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.pill--open {
  color: var(--lit);
  border: 1px solid var(--lit);
}
.pill--paused {
  color: var(--danger);
  border: 1px solid var(--danger);
}
.pill--pending {
  color: var(--quiet);
  border: 1px solid var(--quiet);
}
.pill--ok {
  color: var(--lit);
  border: 1px solid var(--lit);
}
.pill--blocked {
  color: var(--danger);
  border: 1px solid var(--danger);
}

/* Whether the stall is taking anyone right now.
   ---------------------------------------------------------------------------
   A dot and a word, where an outlined OPEN pill used to be. The pill read as a
   button — a bordered capsule beside a heading is a control everywhere else on
   a phone — and it shouted a state that is true most of the time. The dot is
   the live signal and carries the colour; the word just says what the dot
   means, in the same turned-down white as the rest of the reading matter.

   "Serving" rather than "Open", because the question a customer is actually
   asking at a stall is whether food is coming out of it. */
.serving {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--quiet);
  white-space: nowrap;
}
.serving::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--lit);
}
/* A stall that has paused. The dot loses the lit colour rather than gaining a
   new one: the palette's green means live, and there is no "live, but off". */
.serving--paused::before {
  background: var(--fainter);
}

/* Not at the stall.
   ---------------------------------------------------------------------------
   The whole page, when the location check says the phone is somewhere else. */
#too-far {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.25rem;
  padding: 2.5rem 0;
}
#too-far p {
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--text);
}
#too-far button {
  appearance: none;
  background: none;
  border: 1.4px solid var(--unlit);
  border-radius: 12px;
  padding: 0.8rem 1.4rem;
  color: var(--lit);
  font-size: 0.9rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
}

/* --- Menu ---------------------------------------------------------------- */

#menu {
  margin-bottom: 1.5rem;
}

/* One dish.
   ---------------------------------------------------------------------------
   Three columns, the same reading order the app's menu screen uses: how long
   it takes, what it is and what it costs, and the control that adds it. The
   prep time leads because it is the number the choice is actually made on
   while somebody is standing at the stall deciding whether to wait. */
.menu-item {
  position: relative;
  display: grid;
  grid-template-columns: var(--tile) 1fr auto;
  align-items: center;
  gap: var(--tile-gap);
  padding: 0.85rem 0;
}

/* Perforated, and starting where the dish's name starts — so the rules divide
   the reading matter into a column rather than cutting each row in half. The
   app's menu draws the same line for the same reason. */
.menu-item + .menu-item::before {
  content: "";
  position: absolute;
  top: 0;
  left: calc(var(--tile) + var(--tile-gap));
  right: 0;
  border-top: 1px dashed var(--edge);
}

/* The prep tile: a raised square holding one figure. */
.menu-item__prep {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: var(--tile);
  height: var(--tile);
  border-radius: 12px;
  background: var(--board);
  border: 1px solid var(--edge);
}
.menu-item__minutes {
  /* A figure, so it takes the display gesture: italic, heavy, tabular. */
  font-style: italic;
  font-weight: 800;
  font-size: 1.25rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.menu-item__min {
  font-size: 0.5rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--fainter);
  margin-top: 1px;
}

/* The diet mark: a rule under the tile rather than a badge beside the name.
   Short of the tile's width and centred, so it reads as belonging to the tile
   and takes no width from the dish. */
.menu-item__diet {
  width: 70%;
  height: 4px;
  border-radius: 2px;
  margin: 5px auto 0;
}
.menu-item__diet--veg { background: var(--veg); }
.menu-item__diet--egg { background: var(--egg); }
.menu-item__diet--non_veg { background: var(--non-veg); }

.menu-item__body {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  min-width: 0;
}
.menu-item__name {
  font-weight: 700;
  font-size: 1rem;
  line-height: 1.25;
}
.menu-item__price {
  /* Under the name rather than in a column of its own. The prices used to be
     centred in a middle column, which left them neither aligned with each
     other nor attached to the dish they belonged to — ₹150, ₹20 and ₹120 each
     sat in a different place. A price belongs to its dish. */
  font-style: italic;
  font-weight: 700;
  font-size: 0.95rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  white-space: nowrap;
}

.menu-item--sold-out .menu-item__name,
.menu-item--sold-out .menu-item__price {
  color: var(--fainter);
  text-decoration: line-through;
}
.menu-item--sold-out .menu-item__prep {
  background: var(--ground);
}
.menu-item--sold-out .menu-item__minutes {
  color: var(--fainter);
}

/* The stepper.
   ---------------------------------------------------------------------------
   Two glyphs and a figure, with no button chrome at all. They were rendering
   as the browser's default buttons — grey boxes with a hairline — because
   nothing in this file had ever styled them: three small slabs in the corner
   of every row, competing with the dish for attention and looking like a
   rendering fault.

   Green because adding a dish is what this page is for; that is the one thing
   on the board the lit colour is licensed for. */
.stepper {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
.stepper__button {
  appearance: none;
  background: none;
  border: 0;
  padding: 0 0.25rem;
  /* A comfortable target on a phone held in one hand at a stall, without
     drawing a box around it. */
  min-width: 2rem;
  height: 2.25rem;
  font-size: 1.5rem;
  line-height: 1;
  font-weight: 700;
  color: var(--lit);
  cursor: pointer;
}

/* ADD, before anything has been added.
   ---------------------------------------------------------------------------
   Outlined in the green turned down, exactly as the customer app draws it:
   what separates ADD from the control that commits the order is fill, not
   colour. Nothing on this list is a filled lit surface, so the pay button at
   the foot of the page still outranks every row above it. */
.stepper__add {
  appearance: none;
  background: none;
  height: 2.375rem;
  padding: 0 1rem;
  border: 1.4px solid var(--unlit);
  border-radius: 10px;
  color: var(--lit);
  font-size: 0.9rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  cursor: pointer;
}
.stepper__count {
  min-width: 1ch;
  text-align: center;
  font-style: italic;
  font-weight: 800;
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  color: var(--fainter);
}
.stepper--active .stepper__count {
  color: var(--text);
}

/* --- Join form ----------------------------------------------------------- */

#join-form {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

#join-form label {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-size: 0.68rem;
  font-weight: 800;
  color: var(--fainter);
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

#join-form input {
  font-size: 1rem;
  padding: 0.85rem 0.9rem;
  /* Sunk into the board rather than drawn on it, matching the app's fields:
     a filled well reads as somewhere to type, an outline reads as a label. */
  background: var(--ground);
  border: 1px solid var(--edge);
  border-radius: 12px;
  color: var(--text);
}
#join-form input::placeholder {
  color: var(--fainter);
}
#join-form input:focus {
  outline: none;
  border-color: var(--lit);
}

/* The two ways into the line.
   ---------------------------------------------------------------------------
   Paying is the one the app is arranged around, so it is the filled, lit
   control. Joining free is real and stays available, but it is drawn as the
   quieter of the two rather than as a second shout. */
#join-form button {
  font-size: 0.95rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 1rem;
  border-radius: 14px;
  border: 1px solid transparent;
  background: var(--lit);
  /* Dark text on the lit green, which is the only combination on this palette
     that clears contrast at this size. */
  color: var(--ground);
  cursor: pointer;
}
#join-form button.button--secondary {
  background: transparent;
  border-color: var(--unlit);
  color: var(--lit);
}
#join-form button:disabled {
  /* Flattened into the board rather than greyed with a new colour. A control
     that cannot be pressed should recede, not announce itself. */
  background: var(--board);
  border-color: transparent;
  color: var(--fainter);
  cursor: not-allowed;
}
#join-form button.button--secondary:disabled {
  background: transparent;
  border-color: var(--edge);
  color: var(--fainter);
}

.error {
  color: var(--danger);
  font-size: 0.85rem;
}

/* --- Post-join status view / token hero ---------------------------------- */

.token-hero {
  display: flex;
  justify-content: center;
  margin: 2.5rem 0 1.5rem;
}

.token-disc {
  width: 9rem;
  height: 9rem;
  border-radius: 50%;
  border: 3px solid var(--lit);
  display: flex;
  align-items: center;
  justify-content: center;
}
.token-disc--waiting {
  animation: token-pulse 2.4s ease-in-out infinite;
}
.token-disc--ready {
  border-color: var(--lit);
}

#token-number {
  font-family: ui-monospace, "SF Mono", "Roboto Mono", monospace;
  font-size: 3rem;
  font-weight: 800;
  color: var(--lit);
}
.token-disc--ready #token-number {
  color: var(--lit);
}

.token-meta {
  text-align: center;
  color: var(--quiet);
  margin-bottom: 0.35rem;
}

.token-final {
  text-align: center;
  font-weight: 700;
  margin-top: 1.5rem;
}

@keyframes token-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(93, 214, 44, 0.35); }
  50% { box-shadow: 0 0 0 10px rgba(93, 214, 44, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .token-disc--waiting {
    animation: none;
  }
}

/* Three things the markup asks for that no rule had ever reached.
   ---------------------------------------------------------------------------
   The same fault the stepper had: named in the HTML and the script, never
   styled, so each fell back to whatever the browser does by default. Found by
   diffing the classes this page uses against the ones this file defines. */

/* What is in the basket, above the breakdown that itemises it. */
.basket-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.85rem 0 0.35rem;
  color: var(--quiet);
  font-size: 0.85rem;
}
.basket-total__amount {
  font-style: italic;
  font-weight: 800;
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* "Opening your UPI app…" and the like: a running commentary on a payment,
   so it is quiet and it is not an error. */
.pay-status {
  font-size: 0.85rem;
  color: var(--quiet);
  text-align: center;
}

/* Paid, on the token screen. Lit, because it is a live fact about this
   order rather than a note about it. */
.token-paid {
  color: var(--lit);
  font-weight: 700;
}

/* The fee breakdown, shown only for pass_through vendors ------------------

   Quiet by default and emphatic only on the total: the customer already knows
   what the food costs, and the number they need to recognise is the one their
   card will be charged. */
.fee-breakdown {
  margin: 0.75rem 0 0;
  padding: 0.75rem 0.85rem;
  background: rgba(244, 239, 228, 0.04);
  border-radius: 10px;
  font-size: 0.9rem;
}
.fee-breakdown__row {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.2rem 0;
  color: var(--quiet);
}
/* Every digit the same width, so the amounts form a column instead of
   shuffling as the figures change. The two-decimal formatting in
   formatBillPrice puts the points in line; this keeps the digits there. */
.fee-breakdown__row > span:last-child {
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.fee-breakdown__row--total {
  margin-top: 0.4rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--edge);
  color: var(--text);
  font-weight: 700;
}

/* ---------------------------------------------------------------------------
   The small print under a menu, and the stall's FSSAI number.

   Deliberately quiet: it is a legal display duty, not something anybody
   scanning a QR code at a stall came to read. It sits at the very bottom, in
   turned-down white, at a size that is legible without competing with the
   dish prices above it.

   Kept in step with apps/customer_app/lib/widgets/menu_footer.dart — same
   three sentences, same order. Two surfaces disagreeing about their own small
   print is the sort of thing nobody notices until it matters.
   --------------------------------------------------------------------------- */
.menu-footer {
  /* Pushed to the bottom of the view rather than sitting directly under the
     content above it: on a stall with three dishes the small print would
     otherwise stop halfway down the screen with nothing beneath it. `auto`
     collapses to nothing the moment the page is long enough to scroll, so a
     full menu is unaffected. The 2rem is the floor. */
  margin-top: auto;
  padding-top: 2rem;
  border-top: 1px solid var(--edge);
}
.menu-footer ul {
  margin: 0;
  /* Room for the marker, which sits outside the text column so the wrapped
     second line of a bullet lines up under the first. */
  padding-left: 1.1rem;
  color: var(--quiet);
  font-size: 0.78rem;
  line-height: 1.5;
}
.menu-footer li + li {
  margin-top: 0.55rem;
}
.fssai {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 1.25rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--edge);
  color: var(--quiet);
  font-size: 0.82rem;
  /* Fourteen digits read as a number to be checked against a certificate, so
     they get the same tabular figures the amounts elsewhere use. */
  font-variant-numeric: tabular-nums;
}
.fssai__mark {
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--text);
}
/* Height-constrained rather than width-constrained, so a mark supplied at a
   different aspect ratio sits on the same line as the number instead of
   deciding how tall the row is. */
.fssai__img {
  /* Taller than the text beside it — see the same constant in
     menu_footer.dart: the mark's letterforms sit in the middle of the box,
     under a rule and over a descender, so matching the text's cap height
     renders it smaller than the number it belongs to. */
  height: 19px;
  width: auto;
  /* The official mark is black on transparent, and this board is dark — so
     the artwork is forced to a flat white and then settled to the same value
     as the text beside it. brightness(0) crushes every colour to black and
     invert(1) lifts it to white, which means the mark comes out right
     whether the supplied file is black, white, or the green-and-white
     version, rather than depending on which one somebody downloaded. */
  filter: brightness(0) invert(1);
  opacity: 0.72;
}
