/* Page frame. Phone width first; the wider rules are the exception. */

.page {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.page__body {
  flex: 1;
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--space-4) var(--space-4) var(--space-7);
}

.topbar {
  background: var(--color-action);
  color: var(--color-text-inverse);
  padding: var(--space-2) var(--space-4);
}

.topbar__inner {
  max-width: var(--content-max);
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* The mark and the name travel together, and the mark never squashes when the
   name is long. */
.topbar__brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 700;
  color: var(--color-text-inverse);
  text-decoration: none;
  margin-inline-end: auto;
}

.mark { flex: none; }

/* A narrow phone gets the mark alone. The name is on the page it leads to, and
   an icon a thumb can hit beats a name cut off mid-word. */
@media (max-width: 26rem) {
  .topbar__brand span { display: none; }
}

.topbar a { color: var(--color-text-inverse); }

/*
 * ...except one styled as a button, which brings its own background and needs
 * its own text colour back. Inheriting the bar's white here puts white text on
 * a white button.
 */
.topbar a.button--secondary { color: var(--color-text); }

.topbar__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* ── The staff menu ─────────────────────────────────────────────────────────── */

/*
 * This was a row of twenty-two links with overflow-x: auto on it, at every width
 * from a 320px phone to a desktop. A scroller with no fade, no snap and no
 * scrollbar tells a seller nothing about the fifteen links off the right edge,
 * so past about the sixth one the rest of the shop simply was not there.
 *
 * It is now one board in two resting states, and the SAME element in both:
 *
 *   narrow   a drawer      showModal(), slid in from the left, over the page
 *   wide     a column      floating on the left, retractable to a rail
 *
 * The opener is left-anchored because that is the edge the drawer comes from.
 */

/*
 * WHICH SHAPE THE MENU IS, AND WHO DECIDES.
 *
 * The panel has two resting states and JavaScript has to know which one it is
 * in — a drawer needs its opener shown, a column needs the retract switch. The
 * condition is not just a width: the till is excluded from the column too (see
 * the bottom of this file), so a controller testing
 * matchMedia("(min-width: 60rem)") gets the till wrong and hides the opener
 * beside a panel that never became a column. That is exactly what happened.
 *
 * So the CSS answers the question and menu_controller.js reads the answer. The
 * breakpoint, and the exception to it, are written once, here.
 */
.page {
  --menu-column: 0;
  --menu-track: var(--menu-width);

  /*
   * How much of the top of the screen the topbar has. The floating board is
   * centred in what is LEFT, not in the whole viewport — centred in the whole
   * viewport it covered the shop's own name at full height, which is what a
   * twenty-link menu with nothing collapsed is.
   *
   * The value here is the topbar's own arithmetic — its tallest control is a
   * --tap-target and it has --space-2 above and below — so the scriptless column
   * is centred correctly too. menu_controller.js overwrites it with the measured
   * height, which is what covers the topbar wrapping to two lines.
   */
  --topbar-height: calc(var(--tap-target) + (var(--space-2) * 2));
}

/* Retracted: the board is away and the track is a rail wide enough for the
   switch. The width it gives up goes straight to the page. */
.page.is-menu-retracted { --menu-track: var(--menu-rail); }

/*
 * It ships `open` so a browser with no scripts gets the links in normal flow —
 * but the UA puts an open dialog at position: absolute, so that has to be
 * undone. :modal only matches while showModal() holds it, which is exactly the
 * distinction needed: this rule is BOTH the scriptless case and the wide
 * column.
 *
 * The dialog itself carries no paint in either state. The BOARD does — see
 * .menu__board — because the retract switch is a sibling of the board and has to
 * stay put while the board slides away behind it.
 */
.menu:not(:modal) {
  position: static;
  width: auto;
  max-width: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  overflow: visible;
}

.menu:modal {
  inset-block: 0;
  inset-inline: 0 auto;
  width: min(19rem, calc(100vw - var(--space-6)));
  max-width: none;
  max-height: none;
  height: 100dvh;
  margin: 0;
  padding: var(--space-3);
  background: none;
  border: 0;
  border-radius: 0;
  overflow: visible;
}

.menu::backdrop { background: rgb(0 0 0 / 62%); }

/*
 * ── The board ─────────────────────────────────────────────────────────────────
 *
 * The menu is a PLAQUE: the same wood, veil, edges and lettering as the light
 * switch in the topbar above it and the cards on the storefront.
 *
 * the_plaques_stop_at_admin says a table of figures on a board is worse than on
 * paper, and that still holds — this is not a table of figures. It is the same
 * kind of thing as the light switch, which has been a board in this very topbar
 * since 12h: a CONTROL, read at a glance and pressed, not read across. So the
 * colours on it are the ones already measured against that wood, and every one
 * of them is checked by spec/design/plaque_spec.rb.
 */
.menu__board {
  display: flex;
  flex-direction: column;
  padding: var(--space-3);
  border: 1px solid var(--plaque-edge-dark);
  border-radius: var(--radius-lg);
  background:
    linear-gradient(var(--plaque-veil), var(--plaque-veil)),
    var(--plaque-image) center / cover no-repeat;
  box-shadow: inset 0 1px 0 var(--plaque-edge-lit), var(--shadow-card);
  color: var(--color-on-plaque);
}

/* The title and the ✕ belong to the drawer. As a column the « switch is the
   control, and the heading of the first group says where you are. */
.menu:not(:modal) .menu__head { display: none; }

.menu__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-block-end: var(--space-2);
}

.menu__title {
  margin: 0;
  font-weight: 700;
  color: var(--color-on-plaque);
}

.menu__close {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--tap-target);
  height: var(--tap-target);
  margin-inline-end: calc(var(--space-2) * -1);
  font-size: var(--text-2xl);
  line-height: 1;
  color: var(--color-on-plaque);
  background: none;
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
}

/* ── A group, and its heading ─────────────────────────────────────────────── */

/*
 * <details>, so shutting one is the platform's job. The controller only records
 * which were shut; with the scripts off every group is open and every link is
 * one scroll away, which is the state this shipped in before it collapsed at all.
 */
.menu__group + .menu__group { margin-block-start: var(--space-1); }

.menu__heading {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-target);
  padding-inline: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-on-plaque-muted);
  cursor: pointer;
  border-radius: var(--radius-md);
  list-style: none;
}

/* The UA marker, in both spellings. The chevron below replaces it, because a
   summary styled as a heading should not also carry a disclosure triangle in
   whatever colour and size the browser felt like. */
.menu__heading::-webkit-details-marker { display: none; }
.menu__heading::marker { content: ""; }

.menu__heading:hover,
.menu__group[open] > .menu__heading { color: var(--color-on-plaque); }

/*
 * The open group does NOT also get a socket. Only one is open at a time now, so
 * it is a chosen position too — but a plate with two things pressed into it says
 * two things are chosen, and the list of links sitting under a heading is
 * already the plainest possible statement that it is the open one. One knob per
 * switch.
 */

.menu__heading:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: -3px;
}

/*
 * A triangle drawn in borders rather than a glyph: a text arrow is a different
 * shape, weight and baseline in every font the shop's phones ship with, and this
 * one has to sit on the cap height of a 12px uppercase word.
 */
.menu__chevron {
  flex: none;
  width: 0;
  height: 0;
  border-inline-start: 5px solid currentColor;
  border-block: 4px solid transparent;
  rotate: 90deg;
}

/* Shut: the chevron points at the group it would open. */
.menu__group:not([open]) .menu__chevron { rotate: 0deg; }

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

.menu__list--foot {
  margin-block-start: var(--space-3);
  padding-block-start: var(--space-2);
  border-block-start: 1px solid var(--plaque-edge-dark);
  box-shadow: inset 0 1px 0 var(--plaque-edge-lit);
}

.menu__link {
  display: flex;
  align-items: center;
  min-height: var(--tap-target);
  padding: 0 var(--space-3);
  text-decoration: none;
  color: var(--color-on-plaque-muted);
  border-radius: var(--radius-md);
}

.menu__link:hover { color: var(--color-on-plaque); }

/*
 * THE PAGE YOU ARE ON IS A DETENT PRESSED INTO THE BOARD.
 *
 * It was a 3px bar down the leading edge — a marker borrowed from the tab row
 * this menu replaced, which is to say from a different object entirely. The
 * light switch had already said how a chosen position looks on this wood, and
 * the filter rows were given it in 12k: the choice is PUSHED IN, with the
 * knob's own two inset edges. Dark along the top, where the rim of the hole
 * shadows what has gone into it; lit along the bottom, where the floor of the
 * socket catches the bulb.
 *
 * So the menu is the plate, every link is a detent on it, and the page being
 * read is the one pressed in. Exactly .lightswitch__knob and .counter-tab--on,
 * to the shadow — three controls, one way of saying "this one".
 *
 * The lettering goes to --color-on-plaque, which is measured against wood
 * LIGHTER than this: a socket is darker than the plate it is cut into, so a
 * colour that clears 4.5:1 on the board clears it by more in the hollow.
 */
.menu__link[aria-current="page"] {
  background: var(--switch-socket);
  color: var(--color-on-plaque);
  font-weight: 600;
  box-shadow:
    inset 0 2px 3px rgb(0 0 0 / 55%),
    inset 0 -1px 0 var(--plaque-edge-lit);
}

/* ── The two switches ─────────────────────────────────────────────────────── */

/*
 * The opener, and the retract switch. Both revealed by menu_controller.js, so
 * neither ever stands there dead on a page whose scripts have not arrived.
 */
.topbar__menu {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--tap-target);
  height: var(--tap-target);
  margin-inline-start: calc(var(--space-2) * -1);
  font-size: var(--text-xl);
  line-height: 1;
  color: inherit;
  background: none;
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
}

/*
 * « and », as a small board of their own — the same wood as the panel, so the
 * handle is plainly part of the thing it moves. It is centred on the panel's own
 * middle and sits on the panel's right edge, which is where a hand reaches for
 * the edge of a sliding door.
 */
.menu__switch {
  z-index: 1;
  display: grid;
  place-items: center;
  width: var(--space-5);
  height: var(--tap-target-lg);
  padding: 0;
  border: 1px solid var(--plaque-edge-dark);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
  background:
    linear-gradient(var(--plaque-veil), var(--plaque-veil)),
    var(--plaque-image) center / cover no-repeat;
  box-shadow: inset 0 1px 0 var(--plaque-edge-lit), var(--shadow-card);
  color: var(--color-on-plaque);
  cursor: pointer;
}

.menu__switch-glyph::before {
  content: "\00AB";           /* « — put it away */
  font-size: var(--text-lg);
  line-height: 1;
}

.menu__switch[aria-expanded="false"] .menu__switch-glyph::before {
  content: "\00BB";           /* » — bring it out */
}

/* Pressed, the switch goes INTO the board, the same way the light switch's knob
   does. Same two inset edges, read the same way round. */
.menu__switch:active {
  box-shadow: inset 0 2px 3px rgb(0 0 0 / 55%), inset 0 -1px 0 var(--plaque-edge-lit);
}

/*
 * ── Motion ────────────────────────────────────────────────────────────────────
 *
 * The slide, in both senses: the drawer coming in over the page, and the board
 * retracting to the rail.
 *
 * transform on the BOARD and the DIALOG, never on an ancestor: a transform on an
 * ancestor of .till-bar or .connection makes it their containing block and
 * demotes them from fixed and sticky with no error and no visual clue. That is
 * the rule spec/design/room_spec.rb exists to hold.
 *
 * `overlay` and `display` are transitioned with allow-discrete so the panel is
 * still in the top layer on the way out; @starting-style is where it comes from.
 * A browser without either simply shows the drawer, which is not a failure.
 */
@media (prefers-reduced-motion: no-preference) {
  .menu {
    transition:
      transform 180ms ease,
      overlay 180ms allow-discrete,
      display 180ms allow-discrete;
  }

  .menu:modal { transform: translateX(0); }
  .menu:not([open]) { transform: translateX(-100%); }

  @starting-style {
    .menu:modal { transform: translateX(-100%); }
  }

  .menu__board { transition: translate 180ms ease, visibility 180ms; }
  .menu__switch { transition: inset-inline-start 180ms ease; }
}

/*
 * ── The column ────────────────────────────────────────────────────────────────
 *
 * A GRID rather than a fixed column and a matching padding, because the topbar
 * wraps: its height is not a number this file can name. display: grid is neither
 * a transform nor a filter, so nothing about .till-bar or .connection changes.
 *
 * minmax(0, 1fr) and not 1fr — a grid track sized to its content lets a wide
 * .table-scroll push the whole layout sideways, which is the bug this replaced.
 *
 * :has(> .menu) keeps the grid off a signed-out page, where no menu renders and
 * the first track would be an empty gutter. Same technique as
 * `body:has(.till-bar)` in till.css.
 *
 * .room-ground and not every screen: room_class gives the till `room-plain`,
 * and the till is a tool rather than a room — it is held in front of a queue and
 * every column of it is a column of bill. It keeps the drawer at all widths.
 * Reversible by dropping .room-ground from this one selector.
 */
@media (min-width: 60rem) {
  .room-ground .page:has(> .menu) {
    --menu-column: 1;
    display: grid;
    grid-template-columns: var(--menu-track) minmax(0, 1fr);
    grid-template-rows: auto auto 1fr;
  }

  .room-ground .page:has(> .menu) > .topbar { grid-column: 1 / -1; }
  .room-ground .page:has(> .menu) > .connection { grid-column: 2; }
  .room-ground .page:has(> .menu) > .page__body { grid-column: 2; }

  /*
   * FLOATING, AND CENTRED ON THE VIEWPORT.
   *
   * The dialog reserves the track and paints nothing — it is a ZERO-HEIGHT
   * anchor. The first version gave it height: 100dvh and made it sticky, which
   * looked right and was wrong twice over: the page then measured the topbar
   * PLUS a full viewport, so every admin screen had a scrollbar and scrolled its
   * own topbar away even when the content was three rows long.
   *
   * So the board is FIXED and centred on the viewport instead. It hangs in the
   * middle of the screen with room above and below it rather than being ruled
   * off against the topbar — which is what collapsing the groups made possible,
   * because a menu that no longer needs the whole column has no reason to
   * occupy it — and it costs the page no height at all.
   *
   * position: fixed is safe here for the same reason .till-bar is: nothing on
   * the way up to the viewport carries a transform, a filter or a perspective.
   * room.css and spec/design/room_spec.rb are what keep that true.
   */
  .room-ground .page > .menu:not(:modal) {
    grid-column: 1;
    grid-row: 2 / -1;
    height: 0;
    overflow: visible;
  }

  /* The topbar keeps the top of the screen and stays above a board that grows
     into it, whatever the arithmetic below says. */
  .room-ground .page:has(> .menu) > .topbar {
    position: relative;
    z-index: 2;
  }

  .room-ground .page > .menu:not(:modal) .menu__board,
  .room-ground .page > .menu:not(:modal) .menu__switch {
    position: fixed;
    inset-block-start: calc((100dvh + var(--topbar-height)) / 2);
    translate: 0 -50%;
  }

  .room-ground .page > .menu:not(:modal) .menu__board {
    inset-inline-start: var(--space-3);
    width: calc(var(--menu-width) - var(--space-5));
    max-height: calc(100dvh - var(--topbar-height) - var(--space-4));
    overflow-y: auto;
  }

  .room-ground .page > .menu:not(:modal) .menu__switch {
    inset-inline-start: calc(var(--menu-width) - var(--space-4));
  }

  /*
   * Away: the board slides out past its own left edge and stops taking clicks;
   * the switch travels to the rail, because it is not inside the board.
   *
   * Written out at the full length of the rules above rather than as
   * `.is-menu-retracted .menu__board`, which is what it was first and which
   * quietly did nothing: the placing rules score four classes and a child
   * combinator, so a shorter selector loses and the board stayed exactly where
   * it was. It LOOKED retracted, because `visibility` had nothing competing with
   * it — the panel vanished on the spot instead of sliding away, and the switch
   * never moved off the board's edge at all.
   */
  .room-ground .page.is-menu-retracted > .menu:not(:modal) .menu__board {
    translate: calc(-100% - var(--space-4)) -50%;
    visibility: hidden;
  }

  .room-ground .page.is-menu-retracted > .menu:not(:modal) .menu__switch {
    inset-inline-start: 0;
  }
}

/*
 * ── When the photograph has to go ─────────────────────────────────────────────
 *
 * A photograph behind a control is the first thing to drop for somebody who has
 * asked for more contrast, exactly as .lightswitch__slot does. The wood becomes
 * a flat sunken surface and every colour on it goes back to the page's own ink.
 */
@media (prefers-contrast: more), (prefers-reduced-transparency: reduce) {
  .menu__board,
  .menu__switch {
    background: var(--color-surface-sunken);
    color: var(--color-text);
  }

  .menu__title,
  .menu__close,
  .menu__link:hover,
  .menu__heading:hover,
  .menu__link[aria-current="page"] { color: var(--color-text); }

  .menu__heading,
  .menu__link { color: var(--color-text-muted); }

  .menu__link[aria-current="page"] {
    background: var(--color-surface);
    box-shadow: none;
  }

  .menu__list--foot {
    border-block-start-color: var(--color-border);
    box-shadow: none;
  }
}

/* The narrow, centred frame the sign-in, PIN and approval screens share. It
   fills what the topbar leaves rather than a whole screen of its own — a full
   100dvh under the bar would push the card off-centre and scroll the page by
   exactly the bar's height. .page is the column that makes flex mean that. */
.gate {
  flex: 1;
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: var(--color-canvas);
}

.gate__card {
  width: 100%;
  max-width: 26rem;
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
}

/* The one screen where the shop introduces itself: a seller signing in at six
   in the morning, and a customer who has followed a link to a PIN pad. */
.gate__brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  text-align: center;
  font-weight: 700;
  color: var(--color-action);
  margin-block-end: var(--space-4);
}

.gate__brand .mark { border-radius: var(--radius-lg); }
