/* The pieces the portal's markup already asked for.
 *
 * Thirty-three class names were in the templates with no rule anywhere, so a
 * filter row, a danger zone and a definition list all rendered as the browser's
 * defaults. They are here rather than in `portal.css` because that file is the
 * design system and this one is the parts list: nothing below defines a token,
 * a font or a colour of its own, and every value refers to the block at the top
 * of `portal.css`.
 *
 * Loaded after `portal.css`, which is what lets a rule here refine an element
 * selector there without an `!important` and without raising specificity.
 *
 * The portal's focus ring is a 2px outline at a 2px offset, so anything that
 * clips reaches 4px past its own edge. Nothing below sets `overflow`, on
 * purpose: a scroller is a clipping box on all four sides, so a row of filters
 * that scrolls sideways eats the ring of whichever control is at its edge. The
 * rows here wrap instead, which also means nothing ends up off screen with no
 * affordance saying it exists.
 */

/* --- forms ----------------------------------------------------------------
 *
 * A form is a column of fields read top to bottom, so it is capped at a measure
 * rather than stretched across a 1360px page. A row of inputs as wide as the
 * window reads as a search box, and the eye loses the label on the way back.
 * -------------------------------------------------------------------------- */

.form {
  max-width: 780px;
}

/* The foot of a form. Wrapping rather than shrinking, because a confirm button
 * whose label is a whole sentence ("Save as version 3 or whatever you typed")
 * is the one that must stay readable.
 *
 * The rule above it is what makes the row read as the end of the form rather
 * than as one more field, which matters most on the long pages where the last
 * fieldset and the buttons would otherwise touch. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 22px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

/* Extra air between the action and the way out of it, so cancel is never the
 * thing a pointer lands on by being 10px away from save. */
.form-actions > .button-primary + .button {
  margin-left: 10px;
}

/* Help beside a button gets its own line rather than squeezing the buttons into
 * two words each. `.help` is styled inside `.field` by `portal.css`; these are
 * the places it appears outside one. */
.form-actions .help,
.row-actions .help,
.state .help {
  flex: 1 1 100%;
  margin: 0;
  max-width: 78ch;
  color: var(--muted);
  font-size: 13px;
}

/* A field that failed validation, marked at the field rather than only at the
 * message. The rule down its left edge is what ties the message underneath to
 * the input above it when a fieldset has fifteen of them. */
.field-invalid {
  padding-left: 10px;
  border-left: 2px solid var(--err);
}

/* One field's validation message.
 *
 * Colour, a filled ground, a rule and a heavier weight, because roughly one man
 * in twelve reads red as a slightly different grey. Shrink-wrapped, so it sits
 * as a marked block against the width of the field rather than as a full-width
 * band that could belong to anything nearby.
 *
 * Inline-block rather than block because this class lands on a `<span>` inside
 * a definition list on the model page and on a `<p>` under an input everywhere
 * else, and both have to look like the same thing. */
.field-error {
  display: inline-block;
  max-width: 100%;
  margin-top: 4px;
  padding: 5px 10px;
  background: var(--err-bg);
  border-left: 2px solid var(--err);
  border-radius: 0 6px 6px 0;
  color: var(--err);
  font-size: 13px;
  font-weight: 600;
}

p.field-error {
  margin-bottom: 10px;
}

/* A position in a hand-sorted list. Narrow, right-aligned and on its own line
 * above the arrows, because the full-width input the base sheet gives every
 * number field is a 300px box around three digits. */
input.input-order {
  display: block;
  width: 4.75em;
  min-width: 60px;
  margin-bottom: 4px;
  text-align: right;
}

/* An informational panel inside a fieldset: what write-only means, or that a
 * prompt never leaves this page. Not a warning, so it takes the primary rule
 * rather than a status colour, and the words carry the meaning either way. */
.notice {
  max-width: 72ch;
  margin: 0 0 14px;
  padding: 11px 14px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-left: 3px solid var(--primary);
  border-radius: 0 8px 8px 0;
  font-size: 14px;
}

/* --- lists and tables -----------------------------------------------------
 *
 * `.table-wrap` already draws the border, the radius and the ground for the
 * tables that sit inside one. Five list tables do not have a wrap, so the class
 * draws its own edge, and the wrapped ones are left alone rather than given a
 * second box inside the first.
 *
 * `border-radius` is ignored on a table with collapsed borders, so the bare
 * case separates them at zero spacing. Nothing doubles up: only the header and
 * the body cells carry a bottom border, and neither touches the table's own.
 * -------------------------------------------------------------------------- */

:not(.table-wrap) > .list {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  border-collapse: separate;
  border-spacing: 0;
}

:not(.table-wrap) > .list > thead > tr > th:first-child {
  border-top-left-radius: 9px;
}

:not(.table-wrap) > .list > thead > tr > th:last-child {
  border-top-right-radius: 9px;
}

/* Lists that carry their own words and need no bullet: a tally by state, a run
 * of alerts, the kiosks at one location. A gap rather than a line height,
 * because half of them hold a pill and a link on the same line. */
.stack,
.plain,
.list-plain {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 12px;
  padding: 0;
  list-style: none;
}

.stack {
  margin-bottom: 0;
}

/* The one that names records rather than facts: the styles a bulk change is
 * about to touch. Divided, because the question the page asks is how many, and
 * a divided list can be counted down the edge. */
.list-plain {
  gap: 0;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.list-plain > li {
  padding: 7px 14px;
  border-bottom: 1px solid var(--border);
}

.list-plain > li:last-child {
  border-bottom: 0;
}

/* A row of controls above a table.
 *
 * Four columns, so labels and their controls alternate two pairs to a line and
 * a label can never wrap away from the control it names. Eight filters become
 * four lines instead of sixteen, which is the whole difference between this and
 * the stack of blocks that was here.
 *
 * The legend is not a grid item: a fieldset's rendered legend sits outside the
 * anonymous box the rest of its contents lay out in. */
.filters {
  margin: 0 0 18px;
}

.filters > fieldset {
  display: grid;
  grid-template-columns: repeat(2, max-content minmax(150px, 1fr));
  align-items: center;
  gap: 10px 12px;
  margin: 0;
}

.filters > fieldset > label {
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

/* Apply and clear land in a track that is a fraction of the row, and a grid
 * item stretches by default, so without this the clear link is a button as wide
 * as a select. */
.filters > fieldset > .button {
  justify-self: start;
}

/* Help text spans the row rather than sitting in a track.
 *
 * The label tracks are `max-content`, which a paragraph of prose reads as "as
 * wide as this sentence". A single filter carrying a two-line explanation
 * therefore set the width of the whole grid and pushed the page 356px wider
 * than the window at 1280. Nothing else in the row had changed; the sessions
 * page is simply the only one whose filters need explaining.
 *
 * Spanning every column puts it on its own line under the control, where it
 * has `1fr` to wrap in and contributes no intrinsic width. `aria-describedby`
 * is what ties it to its input, so the association does not depend on it
 * sitting in the adjacent cell. */
.filters > fieldset > .help {
  grid-column: 1 / -1;
  max-width: 68ch;
  margin: -2px 0 4px;
}

@media (max-width: 1200px) {
  .filters > fieldset {
    grid-template-columns: max-content minmax(150px, 1fr);
  }
}

/* One column, because two do not fit.
 *
 * `max-content` plus `minmax(150px, 1fr)` has a floor of roughly 300px before
 * the gap, and a label like "Error text contains" pushes it to 466. In a 390px
 * window that is the widest thing on the page, so the filters were what made
 * the document scroll sideways rather than any table. The label goes above its
 * control here instead of beside it. */
@media (max-width: 560px) {
  .filters > fieldset {
    grid-template-columns: minmax(0, 1fr);
    gap: 4px 0;
  }

  .filters > fieldset > label {
    margin-top: 8px;
  }

  .filters > fieldset > .button {
    margin-top: 10px;
    justify-self: stretch;
    text-align: center;
  }
}

/* The buttons that act on whatever is ticked in the table above. Its own ground
 * so it reads as belonging to the table rather than to the page, and the count
 * of what is selected sits in the row with the actions it applies to. */
.row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.row-actions [data-selection-count] {
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
}

/* Change several at once: a panel of its own below the list, because it is a
 * second job on the same page and the table above is the thing it reads from.
 *
 * Its four choices sit across the panel rather than down the left of it. Each
 * one is a select holding two or three words, and a column of them under a
 * 1100px panel is four short controls with an empty half page beside them.
 * Capped at 320px so a select does not stretch to a width that suggests free
 * text goes in it. */
.bulk {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 320px));
  justify-content: start;
  align-items: start;
  gap: 0 18px;
  margin-top: 22px;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow);
}

.bulk > h2,
.bulk > fieldset,
.bulk > .form-actions {
  grid-column: 1 / -1;
}

.bulk > h2 {
  margin-top: 0;
  font-size: 17px;
}

/* Which kiosks, as a wrapping row. One kiosk per line is a list as tall as the
 * fleet, and the choice is a set of short names. */
.bulk > fieldset.field {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
}

/* A label wrapping its own input is not a field label, so it drops the bold the
 * base sheet gives one and becomes a row instead. */
.bulk fieldset.field > label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-weight: 400;
}

/* The number and the two arrows in a sortable table. Nowrap so "Up" and "Down"
 * stay on one line as a pair; the input is a block above them, which keeps the
 * column about 110px wide instead of pushing the eleven columns beside it. */
td.cell-order {
  white-space: nowrap;
}

td.cell-order .button {
  margin-right: 4px;
}

/* A button inside a table row or a cell. Smaller than a form's button because a
 * row is 38px tall and this is a pointer surface, not the kiosk. */
.button-small {
  padding: 3px 9px;
  gap: 4px;
  border-radius: 6px;
  font-size: 13px;
}

/* --- status ---------------------------------------------------------------
 *
 * What something has, and what it is still missing. Both put the reading above
 * the thing it describes rather than beside it, because an operator arriving on
 * one of these pages is answering "is this working" before anything else.
 * -------------------------------------------------------------------------- */

.state {
  margin: 0 0 22px;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow);
}

.state > h2 {
  margin-top: 0;
  font-size: 17px;
}

.state > form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
}

/* Label and value in two columns, divided, so six readings can be scanned down
 * the values without the labels getting in the way. Collapses to one column
 * where two would leave the value four words wide.
 *
 * The space between the columns is padding rather than a column gap, because a
 * gap falls between the two halves of each divider and leaves a row split into
 * two short rules that read as two rows. */
.facts {
  display: grid;
  grid-template-columns: minmax(140px, max-content) minmax(0, 1fr);
  gap: 0;
  margin: 0 0 16px;
}

.facts > dt {
  padding: 8px 20px 8px 0;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-weight: 600;
}

.facts > dd {
  margin: 0;
  padding: 8px 0;
  border-top: 1px solid var(--border);
}

.facts > dt:first-of-type,
.facts > dt:first-of-type + dd {
  border-top: 0;
}

@media (max-width: 720px) {
  .facts {
    grid-template-columns: minmax(0, 1fr);
  }

  .facts > dt {
    padding-bottom: 0;
    border-top: 1px solid var(--border);
  }

  .facts > dd {
    padding-top: 2px;
    border-top: 0;
  }
}

/* Which languages an object is filled in for. The mark and the code are already
 * in the markup, so colour is the third signal rather than the only one, and a
 * missing language is the one with no fill and a bullet instead of a tick. */
.completeness {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 12px;
  vertical-align: middle;
}

.completeness > li {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 8px;
  background: var(--surface-sunk);
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  white-space: nowrap;
}

.completeness > .is-complete {
  background: var(--ok-bg);
  border-color: currentcolor;
  color: var(--ok);
}

.completeness > .is-incomplete {
  color: var(--muted);
}

/* The title on this abbreviation is the same string as its content, so the
 * dotted underline promises an expansion that does not exist. */
.completeness abbr {
  text-decoration: none;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
}

/* Everything a provider declares, folded away. Open by choice rather than by
 * default: it is thirty rows of reference next to a form somebody came here to
 * fill in. The disclosure triangle is left alone, because changing `display` on
 * a summary takes the marker with it and the marker is the affordance. */
.reference {
  margin: 18px 0;
}

.reference > summary {
  width: fit-content;
  padding: 6px 10px;
  border-radius: 7px;
  cursor: pointer;
  font-weight: 550;
  transition: background-color var(--dur-micro) var(--ease-out);
}

.reference > summary:hover {
  background: var(--surface-sunk);
}

.reference[open] > summary {
  margin-bottom: 10px;
}

/* --- sections and detail pages -------------------------------------------- */

/* Two related readings side by side: where a style reaches, and where its
 * category reaches. Each in its own box, because the point of the page is that
 * the two sets differ and a shared background makes them one list. */
.two-up {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 14px;
  margin: 0 0 22px;
}

.two-up > section {
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.two-up > section > h2 {
  margin-top: 0;
  font-size: 16px;
}

.two-up ul {
  margin: 0 0 10px;
  padding-left: 20px;
}

/* How a visitor reads a legal document, and the document itself. Capped at a
 * reading measure and set looser than the rest of the portal, because this is
 * the one place an operator reads prose rather than scanning a table. */
.preview {
  margin: 22px 0;
}

.document {
  max-width: 68ch;
  padding: 22px 26px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 10px;
  line-height: 1.65;
  /* Operator text can hold a URL or a company name with no spaces in it, and
     the alternative to breaking it is a document wider than its box. */
  overflow-wrap: break-word;
}

.document > h3 {
  margin-bottom: 12px;
  font-size: 20px;
}

.document > p:last-child {
  margin-bottom: 0;
}

/* Legal Markdown: the editor and the preview sit as a pair, because writing
 * a policy without seeing the groups is how an operator ships a wall of
 * paragraphs they thought they had sectioned. The form cap is lifted here so
 * the two columns have room; the preview itself stays on a reading measure. */
.legal-form {
  max-width: none;
  display: grid;
  gap: 22px;
}

.legal-form > .form-group {
  margin-bottom: 0;
}

.legal-live {
  display: grid;
  gap: 10px;
  align-content: start;
}

.legal-live__head h3 {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.legal-live__head .muted {
  margin: 0;
}

.legal-doc {
  display: grid;
  gap: 16px;
}

.legal-doc__title {
  margin: 0;
  font-size: 22px;
  letter-spacing: -0.02em;
}

.legal-doc__meta {
  margin: 0;
  color: var(--text-muted);
  font-size: 13px;
}

.legal-doc__lede {
  margin: 0;
  font-size: 16px;
}

.legal-doc__section {
  display: grid;
  gap: 8px;
}

.legal-doc__heading {
  margin: 0;
  font-size: 16px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.legal-doc__section p,
.legal-doc ul {
  margin: 0;
}

.legal-doc ul {
  padding-left: 1.2em;
}

.legal-doc__empty {
  margin: 0;
}

@media (min-width: 1100px) {
  .legal-form {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
  }

  .legal-form > .form-group:first-of-type,
  .legal-form > .form-actions {
    grid-column: 1 / -1;
  }

  .legal-live {
    position: sticky;
    top: 16px;
  }
}

/* Language and orientation for the kiosk preview. Chips rather than a row of
 * links, so the one in force is marked by its fill, its border and its weight
 * as well as by `aria-current`. */
.preview-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 28px;
  margin: 0 0 16px;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 14px;
}

.preview-controls p {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0;
  color: var(--muted);
}

.preview-controls a {
  padding: 3px 11px;
  background: var(--surface-sunk);
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  color: var(--text);
  text-decoration: none;
  transition: background-color var(--dur-micro) var(--ease-out);
}

.preview-controls a:hover {
  background: var(--surface-raised);
}

.preview-controls a[aria-current] {
  background-color: var(--surface-raised);
  background-image: var(--sheen);
  border-color: var(--primary);
  font-weight: 650;
}

/* A poster in a list row. Small, because the row already carries a name, three
 * pills and eight other columns, and the picture is here to tell one style from
 * another at a glance rather than to be looked at. */
img.thumb {
  display: block;
  width: 32px;
  height: auto;
  margin-bottom: 4px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 4px;
}

/* What a style has uploaded. Auto-fill rather than a fixed pair, because a
 * style may have a poster, a loop, both or neither. */
.asset-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 14px;
  margin: 16px 0 0;
}

.asset-grid figure {
  margin: 0;
  padding: 12px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.asset-grid img,
.asset-grid video {
  display: block;
  max-width: 100%;
  height: auto;
  background: var(--bg);
  border-radius: 7px;
}

.asset-grid figcaption {
  margin-top: 8px;
  color: var(--muted);
  font-size: 13px;
}

/* A second row of links inside one object: what it is, its languages, its
 * pictures, where it shows. Wraps rather than scrolls, so the fifth tab is
 * never the one nothing on screen admits to having.
 *
 * Full width inside `.page-head`, which is a `space-between` flex row: without
 * this the tabs would sit out to the right of the heading. */
.subnav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 6px;
  padding: 4px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.page-head > .subnav {
  flex: 0 0 100%;
}

.subnav a {
  padding: 6px 12px;
  border-radius: 7px;
  color: var(--muted);
  text-decoration: none;
  font-weight: 500;
  white-space: nowrap;
  transition:
    background-color var(--dur-micro) var(--ease-out),
    color var(--dur-micro) var(--ease-out);
}

.subnav a:hover {
  background: var(--surface);
  color: var(--text);
}

.subnav a[aria-current] {
  background-color: var(--surface-raised);
  background-image: var(--sheen);
  box-shadow: inset 0 0 0 1px var(--border-strong);
  color: var(--text);
  font-weight: 650;
}

/* --- the irreversible ones -----------------------------------------------
 *
 * Deleting a price book, a category or a model, and revoking a kiosk. Its own
 * ground and its own border rather than a `.card`, so it cannot be mistaken for
 * the section above it by somebody scrolling to the bottom of a form.
 *
 * The status ground rather than the status colour, because `--err-bg` is a deep
 * maroon on this palette and a pale pink on the light one, and both read as
 * caution at a glance without turning a page that holds three of these into a
 * wall. The heading, the border and the button's outline are where the red
 * actually is, and the heading says "delete" in words regardless.
 * -------------------------------------------------------------------------- */

.danger-zone {
  margin: 34px 0 0;
  padding: 16px 18px;
  background: var(--err-bg);
  border: 1px solid var(--err);
  border-radius: 10px;
}

.danger-zone > h2 {
  margin-top: 0;
  color: var(--err);
  font-size: 17px;
}

.danger-zone p {
  max-width: 72ch;
}

.danger-zone p:last-of-type {
  margin-bottom: 14px;
}

/* The button needs a ground of its own here, because the base sheet's danger
 * button fills with `--err-bg` on hover and that is what it is already sitting
 * on. Filling with `--err` instead is not an option: the label would then be
 * white on a light red at about 2.5:1. */
.danger-zone .button-danger {
  background: var(--surface);
}

.danger-zone .button-danger:hover {
  background: var(--surface-sunk);
}

/* --- the kiosk preview ---------------------------------------------------
 *
 * One style laid out at the kiosk's real logical size and scaled down whole, so
 * a name that overflows on the panel overflows here in the same place.
 *
 * This used to be two definitions: these rules, and a nonce'd `<style>` block
 * in `template_preview.html` that repeated all of them and won by coming
 * later. The block existed because the stage must not follow the portal into
 * light mode, and a portal token cannot name the dark value from inside a
 * light theme. Two copies of the same geometry is how one of them ends up
 * wrong, so the palette is re-declared here as local properties instead and
 * the block is gone.
 *
 * The literals are the kiosk's own tokens, copied from
 * `static/kiosk/css/tokens.css`, which is the point: what this page is for is
 * showing the operator the kiosk, so it has to be the kiosk's palette and not
 * whatever the portal is wearing. They cannot be `var()` because the kiosk's
 * stylesheets are not loaded on this origin.
 * ------------------------------------------------------------------------- */

.kiosk-frame {
  --kiosk-ground: #03020a;
  --kiosk-surface: #121019;
  --kiosk-text: #f5f3fa;
  --kiosk-text-muted: #a79fbc;
  --kiosk-brand-gradient: linear-gradient(135deg, #8b5cf6, #ec4899);

  /* .34 puts a 1920px tall stage in about 650px, which is a portrait kiosk
     beside the form on a 900px window without scrolling the page. */
  --scale: 0.34;

  width: calc(1080px * var(--scale));
  height: calc(1920px * var(--scale));
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 12px;
}

.kiosk-frame.is-landscape {
  width: calc(1920px * var(--scale));
  height: calc(1080px * var(--scale));
}

/* A smaller scale on a phone, or the frame is wider than the window.
 *
 * .34 of 1080 is 367px and the content column at 390 is 342, so the portrait
 * frame overhung it and the page scrolled. Landscape is the binding case: .34
 * of 1920 is 653, nearly twice the column, so it needs a scale of its own
 * rather than a shared one that would leave the portrait preview needlessly
 * tiny. The stage inside is still laid out at the kiosk's real size, so this
 * only changes how far away the operator is standing. */
@media (max-width: 560px) {
  .kiosk-frame {
    --scale: 0.28;
  }

  .kiosk-frame.is-landscape {
    --scale: 0.17;
  }
}

.kiosk-frame.is-landscape .kiosk-stage {
  flex-direction: row;
  align-items: center;
  width: 1920px;
  height: 1080px;
}

.kiosk-stage {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 32px;
  width: 1080px;
  height: 1920px;
  padding: 64px 48px;
  background: var(--kiosk-ground);
  color: var(--kiosk-text);
  transform: scale(var(--scale));
  transform-origin: top left;
}

.kiosk-art {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: center;
  min-height: 0;
  background: var(--kiosk-surface);
  border-radius: 24px;
  overflow: hidden;
}

.kiosk-art img,
.kiosk-art video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.kiosk-name {
  margin: 0;
  font-size: 96px;
  line-height: 1.05;
}

.kiosk-description {
  max-width: 24ch;
  margin: 0;
  color: var(--kiosk-text-muted);
  font-size: 32px;
  line-height: 1.35;
}

/* 96px tall because that is the kiosk's touch minimum, and seeing it at the
 * real proportion is the reason this page exists. */
.kiosk-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 96px;
  background: var(--kiosk-brand-gradient);
  border-radius: 999px;
  font-size: 32px;
}

.kiosk-missing {
  padding: 48px;
  color: var(--kiosk-text-muted);
  font-size: 32px;
  text-align: center;
}

/* --- the overview --------------------------------------------------------
 *
 * The morning page, and the only page in the portal somebody reads rather than
 * searches. The fleet first, then trade as a line chart, then attention.
 *
 * Nothing below declares a colour, a font or a radius of its own, like the rest
 * of this file: the values are `portal.css`'s tokens.
 * ------------------------------------------------------------------------- */

/* The state of what is below and the way onward, beside the heading rather than
   in a footnote under it: how fresh the figures are on the overview, and whether
   the stream is still live on the grid. Both are things that qualify the whole
   page, and both are invisible where they were. */
.head-aside {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
  margin: 0;
}

/* A section's own heading row: the name of the thing on the left, the state of
 * it or the way into it on the right, and any explanation on a line below.
 *
 * A grid rather than a wrapping flex row, because the explanation has to be on
 * its own line and asking flex to put it there does not work: a flex item wraps
 * on its hypothetical main size, which is the basis clamped by `max-width`, so
 * `flex: 1 1 100%` with a measure on it wraps only when the measure does not
 * fit. Today's sentence is 80ch, which fitted beside a two-word heading, and
 * the section read as a heading with a paragraph stapled to its right. Two
 * declared tracks and an explicit `1 / -1` cannot come out any other way. */
.panel-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) max-content;
  align-items: baseline;
  gap: 6px 12px;
  margin-bottom: 10px;
}

.panel-head > h2 {
  margin: 0;
  font-size: 17px;
}

.panel-head > p {
  grid-column: 1 / -1;
  margin: 0;
  max-width: 80ch;
}

.board {
  --card-pad: 12px;
  margin-bottom: 12px;
}

/* One short toolbar. Menus drop over the chart so opening Kiosks or Dates
   does not push the page down. */
.board-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  margin: 0 0 10px;
  padding: 6px 8px;
  background-color: var(--surface);
  background-image: var(--sheen);
  border: 1px solid var(--border);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow);
}

.board-filter > select,
.board-filter > input[type="search"] {
  width: auto;
  max-width: 9.5em;
}

.board-filter > input[name="q"] {
  max-width: 18em;
  min-width: 12em;
}

.board-filter > select[name="kiosk"],
.board-filter > select[name="action"],
.board-filter > select[name="actor"] {
  max-width: 16em;
}

.board-filter__when input[type="date"] {
  width: auto;
}

.board-filter__pick {
  position: relative;
}

.board-filter__pick > summary {
  cursor: pointer;
  list-style: none;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--surface-sunk);
  font-size: 13px;
  font-weight: 550;
}

.board-filter__pick > summary::-webkit-details-marker {
  display: none;
}

.board-filter__pick[open] > summary {
  border-color: var(--border-strong);
}

.board-filter__pick > fieldset {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: var(--z-raised);
  display: grid;
  gap: 6px;
  min-width: min(16rem, calc(100vw - 32px));
  max-width: min(22rem, calc(100vw - 32px));
  max-height: 16rem;
  margin: 0;
  padding: 8px;
  overflow: auto;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  box-shadow: var(--shadow);
}

.board-filter__pick > fieldset > label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 8px;
  font-size: 13px;
}

/* Dates sit in the toolbar only when the period is Dates, so the default
   row is When, kiosks, Show. Changing the select reveals them at once. */
.board-filter__dates {
  display: none;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  min-width: 0;
}

.board-filter:has(#period option[value="custom"]:checked) .board-filter__dates {
  display: flex;
}

.board-filter__dates > label {
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}

.board-filter__dates > input[type="date"] {
  width: auto;
  max-width: 11em;
  min-width: 0;
  flex: 0 1 11em;
}

.chart-totals__income::before { background: #34d399; }
.chart-totals__spent::before { background: #f87171; }
.chart-totals__profit::before { background: #8b5cf6; }
.chart-totals__image::before { background: #8b5cf6; }
.chart-totals__video::before { background: #ec4899; }

.money-chart {
  display: block;
  width: 100%;
  height: auto;
  min-height: 160px;
  margin: 0;
  overflow: visible;
}

.money-chart__area {
  stroke: none;
}

.money-chart__area--income { fill: #34d399; fill-opacity: 0.16; }
.money-chart__area--spent { fill: #f87171; fill-opacity: 0.16; }
.money-chart__area--profit { fill: #8b5cf6; fill-opacity: 0.12; }
.money-chart__area--image { fill: #8b5cf6; fill-opacity: 0.2; }
.money-chart__area--video { fill: #ec4899; fill-opacity: 0.2; }

.money-chart__line {
  fill: none;
  stroke-width: 3;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.money-chart__line--income { stroke: #34d399; }
.money-chart__line--spent { stroke: #f87171; }
.money-chart__line--profit { stroke: #8b5cf6; }
.money-chart__line--image { stroke: #8b5cf6; }
.money-chart__line--video { stroke: #ec4899; }

.money-chart__dot--income { fill: #34d399; }
.money-chart__dot--spent { fill: #f87171; }
.money-chart__dot--profit { fill: #8b5cf6; }
.money-chart__dot--image { fill: #8b5cf6; }
.money-chart__dot--video { fill: #ec4899; }

.money-chart__grid {
  stroke: var(--border);
}

.money-chart__label {
  fill: var(--muted);
  font-size: 11px;
}

.board-empty {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: 12px;
}

.chart-numbers {
  margin-top: 6px;
}

.chart-numbers > summary {
  cursor: pointer;
  width: fit-content;
  font-size: 12px;
  font-weight: 550;
  color: var(--muted);
}

.chart-numbers[open] > summary {
  margin-bottom: 6px;
}

.list-tight th,
.list-tight td {
  padding: 5px 8px;
}

.fleet-band {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
  gap: 12px 16px;
}

.kind-card {
  min-width: 0;
  padding: 8px 10px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.kind-card > h3 {
  margin: 0 0 8px;
  font-size: 13px;
}

.kind-card > dl {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px 8px;
  margin: 0;
}

.kind-card > dl > div {
  min-width: 0;
}

.kind-card dt {
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
}

.kind-card dd {
  margin: 0;
  font-size: 14px;
  font-weight: 650;
}

.chart-numbers .pager {
  margin-top: 8px;
}

@media (max-width: 900px) {
  .fleet-band {
    grid-template-columns: minmax(0, 1fr);
  }

  .kind-card > dl {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 720px) {
  .board-filter__pick > fieldset {
    position: static;
    min-width: 0;
    max-width: none;
    width: 100%;
    margin-top: 4px;
  }

  .board-filter__pick[open] {
    flex: 1 1 100%;
  }

  .board-filter__dates {
    flex: 1 1 100%;
  }

  .board-filter__dates > input[type="date"] {
    flex: 1 1 8em;
    max-width: none;
  }
}


/* --- what needs a person ------------------------------------------------- */

/* A disclosure, not a banner. `display` is left alone on `summary` because
 * changing it takes the marker with it and the marker is the affordance. The
 * heading sits as ordinary text beside that marker. */
.attention {
  margin-top: 20px;
  margin-bottom: 0;
}

.attention > summary {
  cursor: pointer;
}

.attention[open] > summary {
  margin-bottom: 12px;
}

.attention-title {
  display: inline;
  margin: 0 8px 0 0;
  font-size: 17px;
}

/* Radios, not a scripted chip group. `:has(:checked)` paints the selected
 * one; the filter that hides rows is the matching `:has` on `.attention`
 * further down, so the list still works with this file's script blocked. */
.attention-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0 0 12px;
  padding: 0;
  border: 0;
}

.attention-filters > label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 13px;
  cursor: pointer;
}

.attention-count {
  min-width: 1.25em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.attention-filters > label:has(:checked) {
  border-color: var(--border-strong);
  background: var(--primary-soft);
}

.attention:has([name="attention-show"][value="error"]:checked) .alert:not(.alert-error),
.attention:has([name="attention-show"][value="warning"]:checked) .alert:not(.alert-warning),
.attention:has([name="attention-show"][value="kiosk"]:checked) .alert:not([data-about="kiosk"]),
.attention:has([name="attention-show"][value="queue"]:checked) .alert:not([data-about="queue"]),
.attention:has([name="attention-show"][value="mail"]:checked) .alert:not([data-about="mail"]),
.attention:has([name="attention-show"][value="spend"]:checked) .alert:not([data-about="spend"]),
.attention:has([name="attention-show"][value="house"]:checked) .alert:not([data-about="house"]) {
  display: none;
}

.attention-empty {
  display: none;
  margin: 0;
  color: var(--muted);
}

.attention:has([name="attention-show"][value="error"]:checked):not(:has(.alert-error)) .attention-empty,
.attention:has([name="attention-show"][value="warning"]:checked):not(:has(.alert-warning)) .attention-empty,
.attention:has([name="attention-show"][value="kiosk"]:checked):not(:has([data-about="kiosk"])) .attention-empty,
.attention:has([name="attention-show"][value="queue"]:checked):not(:has([data-about="queue"])) .attention-empty,
.attention:has([name="attention-show"][value="mail"]:checked):not(:has([data-about="mail"])) .attention-empty,
.attention:has([name="attention-show"][value="spend"]:checked):not(:has([data-about="spend"])) .attention-empty,
.attention:has([name="attention-show"][value="house"]:checked):not(:has([data-about="house"])) .attention-empty {
  display: block;
}

/* Three columns, so a run of alerts can be read down any one of them: what kind
 * of thing it is, what has happened, and the page that fixes it. They used to
 * be one wrapping line each, which put the buttons at seven different x
 * positions and the sentences behind them.
 *
 * The state is a word in a pill and the severity is also the colour of the rule
 * down the left edge, which is the second signal rather than the only one. */
.alerts {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.alert {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr) max-content;
  align-items: center;
  gap: 4px 12px;
  padding: 9px 12px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: 0 8px 8px 0;
}

.alert-error { border-left-color: var(--err); }
.alert-warning { border-left-color: var(--warn); }

.alert > p {
  margin: 0;
}

.alert > .button {
  justify-self: end;
}

.alerts-clear {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin: 0;
}

/* One column, because three do not fit. The pill and the button keep their own
   width rather than stretching to the row. */
@media (max-width: 720px) {
  .alert {
    grid-template-columns: minmax(0, 1fr);
    justify-items: start;
  }

  .alert > .button {
    justify-self: start;
  }
}

/* --- today --------------------------------------------------------------- */

.today {
  margin-bottom: 22px;
}

.figure-group + .figure-group {
  margin-top: 14px;
}

/* The same treatment as a group of sections in the sidebar, and for the same
   reason: it is a label on a set rather than a heading somebody reads. */
.group-label {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px;
}

.figure {
  padding: 13px 14px;
  background-color: var(--surface);
  background-image: var(--sheen);
  border: 1px solid var(--border);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow);
}

.figure-label {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
}

/* The number is the reason the card exists, so it gets the size. Tabular
   figures come from `body`, so a row of them does not shuffle sideways. */
.figure-value {
  margin: 2px 0 10px;
  font-size: 30px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

/* Two readings in two columns rather than the sentence that was here, which
 * read "nothing to compare with on yesterday, and nothing to compare with on
 * last week" under every figure on a quiet day: forty words saying that the
 * business has not opened yet. */
.figure-change {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 2px 10px;
  margin: 0 0 10px;
  font-size: 12px;
}

.figure-change > dt {
  color: var(--muted);
}

.figure-change > dd {
  margin: 0;
  font-weight: 550;
}

/* Which way is the good way depends on the figure, and the view decides: a rise
 * in revenue and a rise in failed sessions are not the same news. Amber rather
 * than red for the bad one, because a dip against yesterday is worth noticing
 * and is not a fault; the faults are in the list at the top of the page. The
 * words say it either way. */
.change-good { color: var(--ok); }
.change-bad { color: var(--warn); }
.change-level { color: var(--muted); }

.figure-note {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
  line-height: 1.45;
}

/* Online and offline as two discs. The colour is the second signal: the
 * word sits under the number, so a greyscale print and a colour-blind
 * reading still name the state. The disc is a ring, not a fill, so the
 * number is ordinary text on the surface and does not have to clear
 * contrast against `--ok` or `--err`. */
.presence {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.presence-item {
  display: grid;
  justify-items: center;
  gap: 4px;
}

.presence-count {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border: 5px solid var(--border);
  border-radius: 50%;
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
}

.presence-item--ok .presence-count {
  border-color: var(--ok);
  color: var(--ok);
}

.presence-item--err .presence-count {
  border-color: var(--err);
  color: var(--err);
}

.presence-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}


/* --- the machinery ------------------------------------------------------- */

/* The fleet, the queue and the spend, side by side where there is room. Each is
 * about six lines long, and full width they were three bands of empty card.
 * `align-items: start` so a short one does not stretch to the tallest. */
.overview-split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
  align-items: start;
  gap: 14px;
}

/* A count per state, as chips rather than as five table rows of which four said
 * "None." The colour is on the number and the state is a word beside it; a state
 * holding nothing is dimmed and its number goes grey, so a fleet with no
 * problems is not a row of red and amber zeroes.
 *
 * Used by the live grid as well as by this section, which is the point: one
 * shape for "how the fleet is split" wherever it is asked. The grid's own
 * version was five list rows of a pill and a number. */
.tally {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 12px;
  padding: 0;
  list-style: none;
}

.tally-item {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 11px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 13px;
}

.tally-count {
  font-size: 15px;
  font-weight: 700;
}

.tally-label {
  color: var(--muted);
}

.tally-item[data-pill="ok"] .tally-count { color: var(--ok); }
.tally-item[data-pill="warn"] .tally-count { color: var(--warn); }
.tally-item[data-pill="err"] .tally-count { color: var(--err); }

.tally-item.is-empty {
  opacity: 0.6;
}

.tally-item.is-empty .tally-count {
  color: var(--muted);
}

/* The kiosks somebody has to go and see, named. Nothing else is named, because a
   list of the fleet is not information a page can act on. */
.named-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.named-list > li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 8px;
  padding: 7px 11px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 8px;
}

/* Four readings off the queue: the number, what it is, and what it does not
 * mean. The last of those is why this is not a row of four stat cards: waiting
 * is not late and held by a stopped worker is not lost, and without the sentence
 * beside each one the section reads as four alarms. */
.readings {
  display: grid;
  gap: 9px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.readings > li {
  display: grid;
  grid-template-columns: minmax(2.4em, max-content) minmax(0, 1fr);
  align-items: baseline;
  gap: 0 10px;
}

.reading-value {
  font-size: 19px;
  font-weight: 700;
  text-align: right;
}

.reading-label {
  font-weight: 600;
}

.reading-note {
  grid-column: 2;
  font-size: 12px;
  line-height: 1.4;
}

.spend-figure {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 10px;
}

.spend-figure > .figure-value {
  margin: 0;
}

/* How much of the cap is gone.
 *
 * A real `progress` rather than a div with a width, because a width has to come
 * from the value and the policy on this origin has no `unsafe-inline` for a
 * style attribute. It also arrives announced, with its own text as the fallback
 * for a browser that draws nothing. The gradient is the vivid pair: there is
 * nothing written on it. */
.meter {
  display: block;
  width: 100%;
  height: 8px;
  margin: 0 0 10px;
  border: 0;
  border-radius: 999px;
  background: var(--surface-sunk);
  -webkit-appearance: none;
  appearance: none;
}

.meter::-webkit-progress-bar {
  background: var(--surface-sunk);
  border-radius: 999px;
}

.meter::-webkit-progress-value {
  background-image: var(--brand-gradient-vivid);
  border-radius: 999px;
}

.meter::-moz-progress-bar {
  background-image: var(--brand-gradient-vivid);
  border-radius: 999px;
}

/* --- the icon sprite ----------------------------------------------------- */

/* Out of the layout without being out of the render tree. `display: none` on a
 * sprite stops `<use>` resolving against its symbols in some browsers, which
 * takes every icon in the sidebar with it, so this is taken out of flow at zero
 * size instead. The element already carries `aria-hidden` in the markup. */
.sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* --- reduced motion -----------------------------------------------------
 *
 * `portal.css` already cuts every duration to near zero for the whole document.
 * This is the same promise kept locally, so the transitions above survive that
 * rule being scoped or rewritten. Nothing here is load bearing: each one is a
 * hover tint on a link or a summary.
 * ------------------------------------------------------------------------ */

/* --- kiosk board -------------------------------------------------------- */

.kiosk-board {
  display: grid;
  gap: 6px;
}

.kiosk-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: 10px 16px;
  padding: 10px 12px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: inherit;
  text-decoration: none;
  transition:
    border-color var(--dur-micro) var(--ease-out),
    background-color var(--dur-micro) var(--ease-out);
}

.kiosk-row:hover,
.kiosk-row:focus-visible {
  border-color: var(--border-strong);
  background: var(--surface-raised);
}

.kiosk-row__mark {
  position: relative;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--surface-sunk);
  border: 2px solid var(--border-strong);
  font-weight: 700;
}

.kiosk-row__mark img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

.kiosk-row__mark::after {
  content: "";
  position: absolute;
  z-index: 1;
  right: -1px;
  bottom: -1px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--muted);
  border: 2px solid var(--surface);
}

/* Default used to be the offline colour, so a missing status, a degraded
   unit and a locked one all read as down. Colour is named on the state. */
[data-status="online"] .kiosk-row__mark::after { background: var(--ok); }
[data-status="degraded"] .kiosk-row__mark::after { background: var(--warn); }
[data-status="offline"] .kiosk-row__mark::after { background: var(--err); }

.kiosk-row__mark--lg {
  width: 72px;
  height: 72px;
  font-size: 28px;
}

.kiosk-row__who {
  display: grid;
  min-width: 0;
  gap: 2px;
}

.kiosk-row__name {
  font-weight: 650;
}

.kiosk-row__state {
  color: var(--muted);
  font-size: 13px;
}

.kiosk-row[data-status="online"] .kiosk-row__state { color: var(--ok); }
.kiosk-row[data-status="degraded"] .kiosk-row__state { color: var(--warn); }
.kiosk-row[data-status="offline"] .kiosk-row__state { color: var(--err); }

.kiosk-row__place,
.kiosk-row__meta {
  color: var(--muted);
  font-size: 13px;
}

.kiosk-row__stats {
  display: grid;
  grid-template-columns: repeat(3, max-content);
  gap: 4px 20px;
  justify-items: end;
  text-align: right;
  align-self: start;
}

.kiosk-row__stat {
  display: grid;
  gap: 0;
  justify-items: end;
}

.kiosk-row__stat--place {
  min-width: 0;
  max-width: 16em;
}

.kiosk-row .kiosk-card__reasons {
  grid-column: 1 / -1;
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--muted);
  font-size: 12px;
}

.kiosk-profile__who {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  margin-bottom: 12px;
}

.kiosk-profile__name {
  margin: 0;
  font-size: 22px;
  font-weight: 700;
}

.kiosk-profile__prices,
.visit-dialog > .kind-card {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  margin: 0;
}

.kiosk-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-end;
}

.kiosk-lock__field {
  display: grid;
  gap: 6px;
  margin: 14px 0 0;
}

.kiosk-lock__field input {
  width: 100%;
}

.kiosk-photo-now {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 16px;
  color: var(--muted);
  font-size: 13px;
}

.kiosk-photo-now img {
  width: 72px;
  height: 72px;
  object-fit: cover;
  border-radius: 50%;
  border: 2px solid var(--border-strong);
}

.visit-board {
  display: grid;
  gap: 4px;
}

.visit {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-sunk);
}

.visit[open] {
  border-color: var(--border-strong);
  background: var(--surface-raised);
}

.visit-row {
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 6px 12px;
  width: 100%;
  padding: 7px 10px;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  list-style: none;
}

.visit-row::-webkit-details-marker {
  display: none;
}

.visit-row::marker {
  content: "";
  display: none;
}

.visit-row:hover,
.visit-row:focus-visible {
  background: color-mix(in srgb, var(--surface-raised) 70%, transparent);
}

.visit-row__mark {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  font-size: 13px;
  font-weight: 700;
}

.visit-row__mark img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.visit-row__mark--photo {
  appearance: none;
  width: 32px;
  height: 48px;
  overflow: hidden;
  border-radius: 6px;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: zoom-in;
}

.visit-row__who {
  display: grid;
  min-width: 0;
  gap: 0;
}

.visit-row__name {
  font-weight: 650;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.visit-row__who .muted {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
}

.visit-row time {
  color: var(--muted);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.visit-row .pill {
  justify-self: end;
}

.visit-more {
  padding: 2px 10px 12px 54px;
}

.visit-fail {
  margin: 0 0 12px;
}

.visit-fail p {
  margin: 0;
}

.visit-fail__label {
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.02em;
  margin-bottom: 4px;
}

/* The way out of a masked row. Last thing in the expand, after the pictures,
   because it is the step an operator takes when the summary was not enough. */
.visit-open {
  display: flex;
  flex-flow: row wrap;
  align-items: baseline;
  gap: 4px 8px;
  margin: 12px 0 0;
  font-size: 13px;
}

.visit-facts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(10em, 1fr));
  gap: 8px 16px;
  margin: 0 0 12px;
}

/* A place's facts are a person, a phone, an email, a clock and a currency.
 * `auto-fill` at 10em made five cells and a leftover on a wide card, and two
 * ragged columns on a narrow one. Two even tracks keep every pair on one
 * line and the wide rows (description, address) spanning both. */
.place-more .visit-facts {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.visit-facts > div {
  min-width: 0;
}

.visit-facts dt {
  color: var(--muted);
  font-size: 12px;
  font-weight: 550;
}

.visit-facts dd {
  margin: 0;
  /* An address is one unbreakable token, and these tracks are about 165px on a
   * desktop window. Without this it drew 29px past its own cell and over the
   * neighbouring value, which reads as two facts printed on top of each other.
   *
   * It is invisible to the sideways-scroll check that guards these pages,
   * because the grid contains the spill: the document stays exactly as wide as
   * the window and the damage is entirely inside it. Any value an operator did
   * not choose the length of wants this, not only the address. */
  overflow-wrap: break-word;
}

.visit-facts__wide {
  grid-column: 1 / -1;
}

.visit-shots {
  display: grid;
  gap: 8px;
  margin: 0 0 14px;
}

.visit-shots h3 {
  margin: 0;
  font-size: 13px;
  font-weight: 650;
  letter-spacing: 0.02em;
}

.visit-media {
  display: grid;
  grid-template-columns: repeat(auto-fill, 160px);
  gap: 10px;
  justify-content: start;
}

.visit-media figure {
  margin: 0;
}

.visit-media__frame {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  overflow: hidden;
  border-radius: 6px;
  background: var(--surface);
  padding: 0;
  border: 0;
  color: inherit;
  font: inherit;
}

button.visit-media__frame {
  cursor: zoom-in;
}

button.visit-media__frame:hover,
button.visit-media__frame:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.visit-media__play {
  position: absolute;
  inset: auto auto 10px 10px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.55);
  pointer-events: none;
}

.visit-media__play::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 54%;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 7px 0 7px 12px;
  border-color: transparent transparent transparent #fff;
  transform: translate(-50%, -50%);
}

.visit-media__blank {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--muted);
  font-size: 13px;
}

.visit-media img,
.visit-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.visit-media figcaption {
  margin-top: 4px;
  color: var(--muted);
  font-size: 12px;
}

@media (max-width: 700px) {
  .visit-row {
    grid-template-columns: 32px minmax(0, 1fr) auto;
  }

  .visit-row time {
    grid-column: 2;
  }

  .visit-more {
    padding-left: 10px;
  }
}

.place-board {
  display: grid;
  gap: 4px;
}

.place {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface-sunk);
}

.place[open] {
  border-color: var(--border-strong);
  background: var(--surface-raised);
}

.place-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto 12px;
  align-items: center;
  gap: 6px 12px;
  width: 100%;
  padding: 7px 10px;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  list-style: none;
}

.place-row::-webkit-details-marker {
  display: none;
}

.place-row::marker {
  content: "";
  display: none;
}

.place-row:hover,
.place-row:focus-visible {
  background: color-mix(in srgb, var(--surface-raised) 70%, transparent);
}

.place-row::after {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 1.75px solid var(--muted);
  border-bottom: 1.75px solid var(--muted);
  transform: rotate(45deg);
  justify-self: center;
  margin-top: -3px;
}

.place[open] .place-row::after {
  transform: rotate(225deg);
  margin-top: 3px;
}

.place-row__who {
  display: grid;
  min-width: 0;
  gap: 0;
}

.place-row__name {
  font-weight: 650;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.place-row__who .muted {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
}

.place-row > .muted {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.place-row .pill {
  justify-self: end;
}

.place-more {
  padding: 2px 10px 12px;
}

.place-kiosks-heading {
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 650;
}

.place-kiosks {
  display: grid;
  gap: 4px;
  margin: 0 0 12px;
  padding: 0;
  list-style: none;
}

.place-kiosks--card {
  margin-top: 10px;
}

.place-kiosks li {
  margin: 0;
  padding: 0;
}

.place-kiosk-card {
  margin-top: 16px;
}

.place-kiosk-card > .muted {
  margin: 0 0 8px;
  max-width: 72ch;
}

.place-kiosk-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
}

.place-kiosk__remove {
  margin: 0;
}

.place-attach-dialog {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(28rem, calc(100vw - 32px));
  max-height: min(32rem, calc(100vh - 48px));
  overflow: auto;
  padding: 20px;
  background: var(--surface-raised);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow);
}

.place-attach-dialog h3 {
  margin: 0 0 8px;
}

.place-attach-dialog .field {
  display: grid;
  gap: 4px;
  margin: 14px 0 0;
}

.place-attach-dialog select {
  width: 100%;
}

.place-attach-dialog .actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}

.place-attach-dialog::backdrop {
  background: rgb(0 0 0 / 0.55);
}

.place-kiosk {
  display: flex;
  flex-wrap: nowrap;
  gap: 10px 12px;
  align-items: center;
  min-height: 44px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: inherit;
  text-decoration: none;
}

a.place-kiosk:hover,
a.place-kiosk:focus-visible {
  background: color-mix(in srgb, var(--surface) 80%, transparent);
  border-color: var(--border-strong);
}

.place-kiosk__mark {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  font-size: 12px;
  font-weight: 700;
}

.place-kiosk__mark img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

.place-kiosk__who {
  display: grid;
  min-width: 0;
  flex: 1 1 auto;
}

.place-kiosk__name {
  font-weight: 650;
}

.place-kiosk__go {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  margin-inline-end: 4px;
  border-inline-end: 2px solid var(--muted);
  border-block-end: 2px solid var(--muted);
  transform: rotate(-45deg);
}

.place-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin: 0;
}

.model-test {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 12px;
  align-items: center;
  margin: 0 0 18px;
}

.model-test > form {
  margin: 0;
}

.place-actions .inline {
  display: inline;
  margin: 0;
}

@media (max-width: 700px) {
  .place-row {
    grid-template-columns: minmax(0, 1fr) auto 12px;
  }

  .place-row > .muted {
    display: none;
  }

  .place-more .visit-facts {
    grid-template-columns: minmax(0, 1fr);
  }
}

.visit-dialog,
.lock-dialog {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(32rem, calc(100vw - 32px));
  max-height: min(36rem, calc(100vh - 48px));
  overflow: auto;
  padding: 20px;
  background: var(--surface-raised);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow);
}

.visit-dialog h3,
.lock-dialog h3 {
  margin: 0 0 12px;
}

.visit-dialog .actions,
.lock-dialog .actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}

.visit-dialog::backdrop,
.lock-dialog::backdrop,
.visit-photo-dialog::backdrop,
.media-preview::backdrop {
  background: rgb(0 0 0 / 0.55);
}

.visit-photo-dialog,
.media-preview {
  width: min(56rem, calc(100vw - 24px));
  max-height: calc(100vh - 24px);
  overflow: auto;
  padding: 16px;
  background: var(--surface-raised);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow);
}

.media-preview__title {
  margin: 0 0 10px;
  font-size: 16px;
  font-weight: 650;
}

.media-preview [hidden] {
  display: none !important;
}

.media-preview__stage {
  margin: 0;
  display: grid;
  place-items: center;
  width: 100%;
  height: calc(100vh - 104px);
  background: var(--surface-sunk);
  border-radius: 6px;
}

.visit-photo-dialog img,
.media-preview img,
.media-preview video {
  display: block;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: calc(100vh - 104px);
  object-fit: contain;
  background: var(--surface-sunk);
  border-radius: 6px;
}

.visit-photo-dialog form,
.media-preview form {
  display: flex;
  justify-content: flex-end;
  margin-top: 12px;
}

@media (max-width: 900px) {
  .kiosk-row {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .kiosk-row__stats {
    grid-column: 1 / -1;
    justify-content: start;
    justify-items: start;
    text-align: left;
  }

  .kiosk-row__meta {
    display: none;
  }

  .kiosk-profile__prices,
  .visit-dialog > .kind-card {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (prefers-reduced-motion: reduce) {
  .subnav a,
  .preview-controls a,
  .reference > summary {
    transition: none;
  }
}

/* Email, privacy and contact studios. Two columns on a wide window so the
 * mark and the connection, or the two letters, sit as a pair rather than as
 * a stack the eye has to climb. */
.studio-pair,
.studio-variants {
  display: grid;
  gap: 16px;
  margin: 0 0 22px;
}

.studio-mark,
.studio-link {
  display: grid;
  gap: 14px;
  align-items: center;
}

.studio-mark {
  grid-template-columns: auto minmax(0, 1fr);
}

.studio-mark__logo {
  width: 72px;
  height: 72px;
  flex: none;
  object-fit: contain;
  display: block;
}

.studio-mark h2,
.studio-link h2,
.variant-card h2 {
  margin: 0 0 6px;
  font-size: 17px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.studio-mark p,
.studio-link p,
.variant-card > header p {
  margin: 0;
}

.studio-link {
  align-content: space-between;
}

.variant-card__head {
  display: grid;
  gap: 6px;
  margin-bottom: 16px;
}

.variant-card__kind {
  margin: 0;
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--primary-hover);
}

.variant-card__langs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}

.variant-card__langs > li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 10px 12px;
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
}

.variant-card__lang {
  margin: 0;
  font-weight: 600;
}

.variant-card__langs .small {
  margin: 2px 0 0;
}

.variant-card__state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}

.studio-langs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 18px;
}

.studio-langs a {
  padding: 6px 14px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  color: var(--text);
  text-decoration: none;
}

.studio-langs a:hover {
  background: var(--surface-raised);
}

.studio-langs a[aria-current="page"] {
  background-color: var(--surface-raised);
  background-image: var(--sheen);
  border-color: var(--primary);
  font-weight: 650;
}

.studio-contacts {
  margin-bottom: 22px;
}

.studio-publish {
  display: grid;
  gap: 8px;
  margin: 0 0 22px;
}

.studio-publish p {
  margin: 0;
}

@media (min-width: 960px) {
  .studio-pair,
  .studio-variants {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: stretch;
  }
}
