/* Admin Panel — the console layout, and nothing else.
   ============================================================================
   Loaded after css/app.css and css/components.css, and standing on both:

     * every colour, radius, space, size and type step is a var() from the
       tokens app.css defines, so a theme or scale change lands here at once;
     * every *component* — button, badge, card, list row, table, field, drawer,
       dialog, modal, toast, tabs, avatar, skeleton, empty state — comes from
       components.css as a `ds-` class on the markup js/admin.js writes. This
       file no longer draws a second button, a second badge or a second table.

   What is left is the part the shared library cannot know: where the sidebar
   sits, how a booking row's five columns are shared out at each width, and the
   two things a console needs that no other surface does (the `⋯` popover and
   the key/value block in the detail drawer).

   Class names are prefixed `ap-`, and every selector below leads with one:
   components.css owns the `ds-` family outright, and a rule here reaching into
   it would be a second definition of a shared component — the exact failure
   tests/test_responsive_qa.py's single-source check exists to prevent. Where a
   ds- component needs a local adjustment, js/admin.js puts an extra `ap-` class
   on the same element and that class is styled here.

   Three decisions worth knowing before reading:

   1. Console shape: sidebar, header, working area. One markup, three widths,
      decided entirely here — js/admin.js never learns which one is on screen,
      so there is one active state and one set of counters rather than three.

        below 768px   the sidebar is an off-canvas drawer behind a burger;
        768–1023px    a 72px icon rail, labels hidden, always visible;
        1024px and up the full labelled sidebar.

      The canon (app.css) has 1280 and 1440: 1280 is where a booking row can
      afford its department column, 1440 is where the working area stops
      growing and centres. 1920 is 1440 with wider gutters, not a fourth shape.

   2. A list is a list. Forty bookings are one bordered container of hairline
      -separated `.ds-row`s, not forty cards with their own shadow — the row is
      48px tall and carries time · room/building · organiser · headcount · one
      status · the primary action. Everything else the status still allows is
      in `.ap-menu` behind `⋯`. The seven-button card this replaces cost about
      four rows of vertical space each and put `Отклонить` two pixels from
      `Одобрить`.

   3. Admin Mode has to be visible without shouting. A gold hairline across the
      very top of the page, a gold ADMIN role badge in the sidebar and a solid
      chrome are enough. A red screen would make the normal case look like an
      incident.

   The literal pixel values below (the rail widths, and the columns that align a
   time or a timestamp against a label) are layout geometry rather than scale
   steps, and are marked where they appear.
   ============================================================================ */

.ap {
  position: relative;
  min-height: 100vh;
  display: block;
  background: var(--bg);
}

/* While the panel is open, both shells step aside: the Mini App's #app and the
   browser product's #cau-web. A class on <body> wins over whatever either
   shell's own script later does to the `hidden` attribute, so there is no
   ordering race between admin.js and the shells' boot code. */
body.ap-admin-open #app,
body.ap-admin-open #cau-web { display: none !important; }

/* The one piece of chrome that says "you are in the admin panel": gold used as a
   rule, which is the one thing the crest does with it. */
.ap-mode-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--space-3);
  z-index: 75;
  background: var(--gold);
}

/* ---------------- sidebar ---------------- */

.ap-side {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 70;
  width: 272px; /* layout geometry: the drawer width on a phone */
  max-width: 84vw;
  display: flex;
  flex-direction: column;
  background: var(--surface-solid);
  border-right: var(--space-1) solid var(--border);
  transform: translateX(-101%);
  /* Same trick as the drawer: switched, not interpolated, so a closed drawer
     leaves the tab order instead of merely sitting off-screen. */
  visibility: hidden;
  transition: transform .24s ease, visibility 0s linear .24s;
}
.ap-side.ap-on {
  transform: none;
  visibility: visible;
  transition: transform .24s ease, visibility 0s;
}
.ap-side-head {
  display: flex;
  align-items: center;
  gap: var(--space-11);
  padding: var(--space-18) var(--space-16) var(--space-14);
  border-bottom: var(--space-1) solid var(--border);
  min-height: var(--size-66);
}
.ap-mark {
  width: var(--size-36);
  height: var(--size-36);
  border-radius: 50%;
  flex: none;
  overflow: hidden;
  background: var(--navy-800);
  display: grid;
  place-items: center;
}
.ap-mark img { width: 100%; height: 100%; display: block; border-radius: 50%; }
.ap-side-name { min-width: 0; }
.ap-side-name b {
  display: block;
  font-size: var(--fs-13-5);
  font-weight: var(--weight-680);
  letter-spacing: -0.2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ap-side-name span {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--fs-9-5);
  font-weight: var(--weight-700);
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: var(--gold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ap-side-foot {
  padding: var(--space-14) var(--space-16) var(--space-20);
  border-top: var(--space-1) solid var(--border);
}

/* The language choice, at the foot of the navigation rather than in the header.
   The pills share .ap-lang with the row they came from; these rules only place
   them and let them use the width they now have. */
.ap-side-lang {
  margin-top: auto;
  padding: var(--space-14) var(--space-16) 0;
}

.ap-side-caption {
  margin: 0 0 var(--space-8);
  font-size: var(--fs-10);
  font-weight: var(--weight-700);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-faint);
}

.ap-side-lang .ap-lang { width: 100%; }
.ap-side-lang .ap-lang button { flex: 1; padding: var(--space-7) var(--space-4); }

/* The dimmer behind the drawer. Never shown from 768px up, where the sidebar is
   part of the layout rather than something laid over it. */
.ap-navscrim {
  position: fixed;
  inset: 0;
  z-index: 65;
  background: var(--scrim);
  opacity: 0;
  visibility: hidden;
  transition: opacity .2s, visibility .2s;
}
.ap-navscrim.ap-on { opacity: 1; visibility: visible; }

/* ---------------- navigation ---------------- */

.ap-nav {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-12);
}
.ap-nav-item {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-11);
  width: 100%;
  min-height: var(--size-40);
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: var(--weight-600) var(--fs-13)/var(--lh-1) var(--font);
  padding: var(--space-10) var(--space-12);
  border-radius: var(--radius-11);
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
  transition: color .18s, background-color .18s, box-shadow .18s;
}
.ap-nav-item:hover { color: var(--text); background: var(--hover); }
.ap-nav-item.ap-on {
  color: var(--text);
  background: var(--accent-soft);
  box-shadow: inset var(--space-2) 0 0 var(--gold);
}
.ap-nav-icon {
  width: var(--size-18);
  height: var(--size-18);
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ap-nav-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Rides on .ds-unread: the count itself is the shared component, this only
   parks it against the icon when the sidebar is a rail. */
.ap-count { flex: none; }

/* ---------------- header and working area ---------------- */

.ap-shell {
  min-width: 0;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
/* Rides on .ds-page-header (sticky, back slot, title/subtitle, actions). The
   shared header is translucent over the page; a console header sits on solid
   chrome so a scrolling table does not read through it. */
.ap-hdr {
  z-index: 30;
  min-height: var(--size-66);
  background: var(--surface-solid);
}
.ap-burger { flex: none; }
.ap-who {
  font-size: var(--fs-11-5);
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: var(--size-180);
}
.ap-lang {
  display: flex;
  gap: var(--space-2);
  padding: var(--space-2);
  flex: none;
  background: var(--surface);
  border: var(--space-1) solid var(--border);
  border-radius: var(--radius-pill);
}
.ap-lang button {
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font: var(--weight-600) var(--fs-11)/var(--lh-1) var(--font);
  padding: var(--space-7) var(--space-10);
  border-radius: var(--radius-pill);
  cursor: pointer;
  letter-spacing: 0.4px;
  transition: background-color .18s, color .18s;
}
.ap-lang button.ap-on { background: var(--surface-2); color: var(--text); }

/* The way back to the ordinary interface. It rides on .ds-btn --secondary, and
   the gold edge is the one thing that marks it as the exit from admin mode; on
   a narrow screen the label still collapses to the arrow, which is now simply
   room the title gets rather than a negotiation with the language pills — those
   are in the navigation. */
.ap-usermode { border-color: var(--gold-line); }
.ap-usermode:hover { background: var(--gold-soft); }
.ap-usermode svg {
  width: var(--size-14);
  height: var(--size-14);
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ap-main {
  flex: 1;
  min-width: 0;
  width: 100%;
  max-width: 1440px;
  padding: var(--space-18) var(--space-16) var(--space-48);
}

/* ---------------- shared blocks ---------------- */

.ap-lead {
  margin: 0 0 var(--space-14);
  font-size: var(--fs-12-5);
  color: var(--text-dim);
}
.ap-lead.ap-foot { margin: var(--space-16) 0 0; }
.ap-block { margin-bottom: var(--space-26); }
.ap-block-title {
  margin: 0 0 var(--space-10);
  font-size: var(--fs-10-5);
  font-weight: var(--weight-700);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-faint);
}
/* `minmax(0, 1fr)` and not the implicit `auto`: an auto track is allowed to be
   as wide as its widest content, and the content here includes the day's
   schedule, which is a timeline. On a phone that pushed the column past the
   screen and took the whole panel sideways with it. The two-column rule at
   1024px already says minmax(0, …) for exactly this reason; the single-column
   default had been left to `auto`. */
.ap-cols { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-20); }
.ap-grid { display: grid; gap: var(--space-10); }
.ap-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-10); }
.ap-toolbar {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--space-12);
  margin-bottom: var(--space-14);
}
/* .ds-field carries a bottom margin for a stacked form; in a toolbar the row's
   own gap is the spacing and a second one would misalign the controls. */
.ap-flush { margin-bottom: 0; }
.ap-narrow { max-width: 190px; }
.ap-search { max-width: 340px; flex: 1; }
.ap-sub {
  margin-top: var(--space-3);
  font-size: var(--fs-11-5);
  color: var(--text-dim);
}
.ap-sub.ap-warn { color: var(--wait); margin-top: var(--space-4); }
.ap-name { font-size: var(--fs-14); font-weight: var(--weight-640); }

/* The filter row above a list. Rides on .ds-tabs — the underline shape, which
   is what four or five section-wide choices want; the pill-track .ds-segmented
   is for two or three on a phone. */
.ap-tabs { margin-bottom: var(--space-14); }

/* .ds-table--sticky only sticks inside a box with a height of its own, and
   .ds-table-wrap deliberately has none — it is the horizontal scroller. This
   gives the two tables that are long enough to need it (users, history) a
   vertical one as well, so the column names are still there at row forty. */
.ap-table-scroll {
  max-height: calc(100vh - 260px);
  overflow-y: auto;
}

/* Avatar and name as one cell. */
.ap-user {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  min-width: 0;
}

/* ---------------- dashboard tiles ---------------- */

.ap-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--space-10);
  margin-bottom: var(--space-20);
}
/* Rides on .ds-card. Only the tiles an admin is meant to act on get an edge;
   the rest are context. Marking everything urgent marks nothing. */
.ap-tile { padding: var(--space-14) var(--space-16); }
.ap-tile.ap-lead-tile { border-color: var(--gold-line); background: var(--gold-soft); }
.ap-tile.ap-alert { border-color: var(--busy); background: var(--busy-soft); }
.ap-tile.ap-alert .ap-tile-value { color: var(--busy); }
.ap-tile-value {
  display: block;
  font-size: var(--fs-20);
  font-weight: var(--weight-680);
  letter-spacing: -0.8px;
  line-height: var(--lh-1-2);
  font-variant-numeric: tabular-nums;
}
.ap-tile-label {
  display: block;
  margin-top: var(--space-4);
  font-size: var(--fs-11-5);
  color: var(--text-dim);
}

/* ---------------- the booking / request row ---------------- */

/* Rides on .ds-row inside .ds-rows: the container, the hairlines, the 48px
   floor and the hover wash are the shared list; this is only the column
   arrangement of one row's five facts, plus where its actions sit. */
.ap-row { gap: var(--space-8); }
.ap-open {
  flex: 1;
  min-width: 0;
  display: grid;
  align-items: center;
  gap: var(--space-2) var(--space-12);
  /* Phone: three lines. The time and the status share the first, because those
     are the two an admin scans for; the room gets the second on its own because
     a truncated room name is a useless room name. */
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "when  state"
    "where where"
    "who   num";
  padding: var(--space-4) 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ap-cell { min-width: 0; }
.ap-cell b {
  display: block;
  font-size: var(--fs-13);
  font-weight: var(--weight-620);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ap-cell small {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--fs-11);
  font-weight: var(--weight-500);
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ap-cell-when { grid-area: when; }
.ap-cell-when b { font-variant-numeric: tabular-nums; letter-spacing: -0.1px; }
.ap-cell-where { grid-area: where; }
.ap-cell-who { grid-area: who; }
/* The department has no line of its own until 1280px, where the row is wide
   enough to give it one; below that it rides under the organiser's name. */
.ap-cell-dept { display: none; }
.ap-cell-num {
  grid-area: num;
  justify-self: start;
  align-self: center;
  padding: var(--space-3) var(--space-9);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: var(--fs-11-5);
  font-weight: var(--weight-620);
  font-variant-numeric: tabular-nums;
}
.ap-cell-state {
  grid-area: state;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.ap-row-act {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

/* Urgency is not a status (STATUS_MATRIX §4): it is `pending`, which is already
   what the `new` chip says. So it rides beside that chip as the one sanctioned
   extra mark rather than becoming a second badge repeating it in louder
   colours. */
.ap-urgent {
  font-style: normal;
  font-size: var(--fs-11);
  line-height: var(--lh-1);
  color: var(--busy);
}

/* ---------------- the schedule fallback ---------------- */

/* Used only when js/roomsui.js is absent or answers in a shape this panel does
   not recognise: one compact line per booking, no actions — those live in the
   card the line opens. */
.ap-room-day { margin-bottom: var(--space-16); }
.ap-room-day-title {
  margin: 0 0 var(--space-6);
  font-size: var(--fs-13);
  font-weight: var(--weight-660);
  display: flex;
  align-items: center;
  gap: var(--space-8);
}
.ap-room-day-title em {
  font-style: normal;
  font-size: var(--fs-11);
  font-weight: var(--weight-500);
  color: var(--text-faint);
}
.ap-line {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr) auto; /* layout geometry: the time column */
  align-items: center;
  gap: var(--space-4) var(--space-10);
  width: 100%;
  min-height: var(--size-40);
  text-align: left;
  background: var(--surface);
  border: var(--space-1) solid var(--border);
  border-radius: var(--radius-11);
  padding: var(--space-8) var(--space-11);
  margin-bottom: var(--space-4);
  cursor: pointer;
  color: inherit;
  font: inherit;
  font-size: var(--fs-12-5);
  transition: border-color .18s, background-color .18s;
}
.ap-line:hover { border-color: var(--border-strong); }
.ap-line-when { font-variant-numeric: tabular-nums; font-weight: var(--weight-640); }
.ap-line-who {
  font-weight: var(--weight-600);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ap-line-num {
  justify-self: end;
  font-size: var(--fs-11);
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.ap-line-note {
  grid-column: 1 / -1;
  font-size: var(--fs-11);
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------------- the `⋯` popover ---------------- */

/* The one component this panel needs and components.css does not carry: an
   anchored menu of the actions a status still allows but which are not the
   obvious next step. Fixed rather than absolute — js/admin.js anchors it from a
   client rectangle, so no scroll offset has to be reasoned about. (Asked of A5
   as `ds-menu`; see the report.) */
.ap-menu {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 86;
  min-width: 196px;
  max-width: 88vw;
  padding: var(--space-6);
  background: var(--surface-solid);
  border: var(--space-1) solid var(--border-strong);
  border-radius: var(--radius-14);
  box-shadow: var(--shadow-2);
  opacity: 0;
  visibility: hidden;
  transition: opacity .14s, visibility 0s linear .14s;
}
.ap-menu.ap-on {
  opacity: 1;
  visibility: visible;
  transition: opacity .14s, visibility 0s;
}
.ap-menu-item {
  display: block;
  width: 100%;
  min-height: var(--size-40);
  text-align: left;
  border: 0;
  background: transparent;
  color: var(--text);
  font: var(--weight-600) var(--fs-13)/var(--lh-1) var(--font);
  padding: var(--space-11) var(--space-12);
  border-radius: var(--radius-11);
  cursor: pointer;
  transition: background-color .14s, color .14s;
}
.ap-menu-item:hover { background: var(--hover); }
.ap-menu-item.ap-danger { color: var(--danger); }

/* ---------------- detail drawer ---------------- */

/* Rides on .ds-drawer --wide: the frame, the slide, the head/body/foot bands
   and the width all come from components.css.

   What stays here is the one contract this panel is held to by name. A drawer
   parked off-screen by a transform alone is invisible to eyes and present to
   the keyboard: its close button still takes focus, and a keyboard user lands
   on a control 400px past the right edge. `visibility` is what actually removes
   it, and it is switched rather than interpolated — flipped at once on the way
   in so the pane is there for the whole travel, held back by exactly the travel
   time on the way out so it does not blink away.

   The travel below is the shared drawer's own .28s curve, restated because this
   declaration replaces its `transition` rather than adding to it; nothing moves
   differently. tests/test_admin_mode_switch.py pins this pair, which is also
   why the open state is `.on` here as well as the shared `.ds-open` — the two
   are set together by js/admin.js. The A3 report asks the Lead to move that
   check onto .ds-drawer in tests/test_design_system.py and collapse the pair. */
.ap-panel {
  visibility: hidden;
  transition: transform .28s cubic-bezier(.32, .72, 0, 1), visibility 0s linear .28s;
}
.ap-panel.on {
  transform: none;
  visibility: visible;
  transition: transform .28s cubic-bezier(.32, .72, 0, 1), visibility 0s;
}
/* `overflow-wrap: anywhere` because this span also carries a corporate
   address on the access drawer: one long token with no break opportunity of
   its own, which at a phone's width ran straight past the drawer's edge. */
.ap-panel-id { font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.ap-actions {
  flex-wrap: wrap;
  justify-content: flex-start;
  gap: var(--space-8);
}
.ap-actions > .ap-grow { flex: 1 1 auto; }

.ap-kv { display: grid; }
.ap-kv > div {
  display: grid;
  grid-template-columns: 118px minmax(0, 1fr); /* layout geometry: one label column */
  gap: var(--space-10);
  align-items: baseline;
  padding: var(--space-8) 0;
  border-bottom: var(--space-1) solid var(--border);
  font-size: var(--fs-13);
}
.ap-kv > div:last-child { border-bottom: 0; }
.ap-kv span {
  font-size: var(--fs-11-5);
  color: var(--text-faint);
}
.ap-kv b {
  font-weight: var(--weight-600);
  word-break: break-word;
}
.ap-kv-wide > div { grid-template-columns: 200px minmax(0, 1fr); }

/* Messaging mounts the thread here. Kept as one empty container so the seam
   between the card and the conversation is a mount point, not a negotiation. */
#ap-conversation { display: block; }

/* ---------------- overlay stacking ---------------- */

/* components.css stacks its overlays at 80 (scrim) / 81 (dialog, modal,
   drawer, sheet) / 82 (toasts). A console layers two of them: a confirmation
   over the open detail drawer. These three classes lift that second layer and
   its own backdrop above the drawer, so the decision is genuinely modal
   instead of leaving a live drawer clickable behind it. */
.ap-scrim-top { z-index: 84; }
.ap-over { z-index: 85; }
.ap-toasts { z-index: 90; }

/* Five codes, one per line, because a grid of five reason chips is read as
   decoration and a list is read as a choice. They are real radios
   (.ds-radio), so the keyboard and the screen reader need nothing extra; the
   confirm button beside them stays disabled until one is chosen, since the
   service refuses a reasonless refusal and the form must not let an admin
   discover that the hard way. */
.ap-reasons {
  display: grid;
  border: var(--space-1) solid var(--border);
  border-radius: var(--radius-11);
  padding: 0 var(--space-12);
}
.ap-reasons > label + label { border-top: var(--space-1) solid var(--border); }
/* Why the confirm button is dead until a code is picked. Hidden the moment one
   is, so it reads as an instruction rather than as a scolding. */
.ap-hint {
  margin: 0;
  font-size: var(--fs-11-5);
  color: var(--text-faint);
}
.ap-hint[hidden] { display: none; }

/* In flight. The disabled attribute is what actually stops the second click;
   this only says so on a control whose disabled look is otherwise subtle. */
.ap-busy { opacity: 0.55; }

/* ---------------- messenger, rooms, history ---------------- */

/* The messenger sizes itself against the viewport rather than its content: a
   chat that grows the page instead of scrolling inside itself loses the
   composer below the fold, which is the one control it exists for. */
.ap-messenger {
  display: flex;
  min-height: 420px;
  height: calc(100vh - 230px);
}

/* The read-only building catalogue: a row of badges, nothing to click. */
.ap-bld-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-bottom: var(--space-8);
}

/* Host for the shared BuildingFilter (js/roomsui.js); the widget styles
   itself, this only gives it breathing room above the grid. */
.ap-bfilter { margin-bottom: var(--space-12); }

/* The release-request block sits after the booking list in Requests. */
.ap-release { margin-top: var(--space-26); }

/* ---------------- rooms: the card, the editor, the photographs ----------------

   Three shapes, and one rule they all serve: a room is never deleted, only
   taken out of service, so nothing below draws a destructive control for a
   room. The only red text in this whole block belongs to removing one
   photograph, which really is a deletion and really is reversible by
   uploading another.
   ---------------------------------------------------------------------- */

/* The section bar: the one primary action, and beside it the sentence that
   accounts for the control an administrator will look for and not find. */
.ap-rooms-bar { align-items: center; }
.ap-rooms-note { flex: 1 1 240px; min-width: 0; margin: 0; }

/* A room card is a cover, a body and a pair of controls. The picture is
   cropped rather than letterboxed: every photograph in the volume is a phone
   snap of a room, and a band of surface colour around one says nothing. */
.ap-room { display: flex; flex-direction: column; }
.ap-room-cover {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  margin-bottom: var(--space-12);
  overflow: hidden;
  border-radius: var(--radius-12);
  background: var(--surface-2);
}
.ap-room-cover img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* No photograph yet: the section's own icon rather than a grey rectangle, so
   an empty cover reads as "nothing uploaded" and not as a failed image. */
.ap-room-cover-blank { display: grid; place-items: center; color: var(--text-faint); }
.ap-room-cover-blank svg { width: var(--size-32); height: var(--size-32); }
.ap-room-body { flex: 1; min-width: 0; }
.ap-room-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8);
}
.ap-room-actions { display: flex; gap: var(--space-8); margin-top: var(--space-12); }
.ap-room-actions > .ap-grow { flex: 1; }

/* A quiet button that still says what it does. Used on the one control in the
   section that removes anything — a photograph — and on the loud half of the
   `⋯` menu. */
.ap-danger { color: var(--danger); }

/* The editor sits in its own band *below* the confirmation's: taking a room
   out of service and removing a photograph both raise a question over this
   form, and a question that lands under the form it interrupts is a dead end.
   84/85 are the dialog scrim and the dialog (see the overlay block above). */
.ap-room-scrim { z-index: 82; }
.ap-room-modal { z-index: 83; }
/* The tab strip is outside .ds-modal__body, so it keeps the modal's own
   horizontal rhythm rather than the body's padding. */
.ap-room-tabs { margin: 0 var(--space-16); }
.ap-room-pane[hidden] { display: none; }

/* One column on a phone, two from tablet up: a label in this system is
   uppercase and letterspaced, and two of them beside two number inputs at
   375px leaves room for neither. */
.ap-form-grid { display: grid; gap: 0 var(--space-14); }
.ap-form-grid .ap-room-field { margin-bottom: var(--space-12); }

/* A directory row that opens. The cursor is the only affordance a table row
   has — there is no border to lift and no shadow to deepen — so it is the whole
   signal, and the focus ring is what says the same thing to a keyboard. */
.ap-row-open { cursor: pointer; }
.ap-row-open:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}
@media (hover: hover) and (pointer: fine) {
  .ap-row-open:hover { background: var(--surface-2); }
}

.ap-photos-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-10);
  margin-bottom: var(--space-10);
}
.ap-photos-count {
  font-size: var(--fs-12-5);
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.ap-photos-note { margin: 0 0 var(--space-10); }

/* Progress, because four megabytes from a phone over university wifi is ten
   seconds of nothing otherwise. The fill is the fact; the word beside it is
   for anybody who cannot watch a six-pixel bar move. */
.ap-progress {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  margin-bottom: var(--space-10);
}
.ap-progress[hidden] { display: none; }
.ap-progress-track {
  flex: 1;
  min-width: 0;
  height: var(--size-6);
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
}
.ap-progress-bar {
  display: block;
  width: 0;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--accent);
  transition: width .18s ease;
}
.ap-progress-text { font-size: var(--fs-11-5); color: var(--text-dim); }

.ap-photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: var(--space-10);
}
.ap-photo {
  position: relative;
  margin: 0;
  overflow: hidden;
  border: var(--space-1) solid var(--border);
  border-radius: var(--radius-12);
  background: var(--surface-2);
}
.ap-photo img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
/* The cover is marked twice — a ring and a word — because the ring alone is a
   colour difference and this grid is exactly where somebody is choosing. */
.ap-photo-is-cover { border-color: var(--accent); }
.ap-photo-mark {
  position: absolute;
  top: var(--space-6);
  left: var(--space-6);
  padding: var(--space-2) var(--space-8);
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--fs-11);
  font-weight: var(--weight-700);
}
.ap-photo-acts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  padding: var(--space-6);
}
.ap-photo-acts > .ap-grow { flex: 1 1 auto; }

/* The audit journal in the drawer: a timestamp column and a verb, dense enough
   that twenty entries are one glance. The section list is a .ds-table; this is
   the same information where a table would be too much furniture. */
.ap-log { display: grid; }
.ap-log-row {
  display: grid;
  grid-template-columns: 132px auto minmax(0, 1fr); /* layout geometry: timestamp column */
  gap: var(--space-4) var(--space-10);
  align-items: baseline;
  width: 100%;
  text-align: left;
  border: 0;
  border-bottom: var(--space-1) solid var(--border);
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--fs-12-5);
  padding: var(--space-9) var(--space-2);
}
.ap-log-row time {
  color: var(--text-faint);
  font-size: var(--fs-11-5);
  font-variant-numeric: tabular-nums;
}
.ap-log-row code {
  font-size: var(--fs-11-5);
  color: var(--gold);
}
.ap-log-row em {
  font-style: normal;
  color: var(--text-dim);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ap-when {
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ap-action { color: var(--gold); }

/* ============================================================
   Narrow phones — 375 and 390 sit here too, this is the floor.
   ============================================================ */

@media (max-width: 430px) {
  .ap-usermode span { display: none; }
  .ap-usermode { padding: var(--space-8) var(--space-10); }
  /* The name is furniture at this width; the exit and the language are not. */
  .ap-who { display: none; }
  .ap-hdr-lead { display: none; }
  .ap-main { padding: var(--space-14) var(--space-12) var(--space-48); }
  /* The action moves to its own line rather than squeezing the row's text into
     nothing: at 375px a truncated organiser name is worse than a taller row. */
  .ap-row { flex-wrap: wrap; }
  .ap-row-act {
    width: 100%;
    justify-content: flex-end;
    padding-top: var(--space-6);
    border-top: var(--space-1) solid var(--border);
  }
  .ap-log-row { grid-template-columns: minmax(0, 1fr); }
  .ap-kv > div { grid-template-columns: minmax(0, 1fr); }
  .ap-kv-wide > div { grid-template-columns: minmax(0, 1fr); }
  /* The modal is a bottom sheet at this width, so the tab strip follows the
     body's narrower gutter. Two photo tiles a row rather than one: choosing a
     cover is a comparison. */
  .ap-room-tabs { margin: 0 var(--space-12); }
  .ap-photos { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); }
  .ap-rooms-note { flex-basis: 100%; }
}

/* ============================================================
   Tablet portrait and up — the sidebar joins the layout as a 72px icon rail.
   ============================================================ */

@media (min-width: 768px) {
  .ap {
    display: grid;
    grid-template-columns: 72px minmax(0, 1fr);
  }
  .ap-side {
    position: sticky;
    top: 0;
    bottom: auto;
    z-index: 40;
    width: auto;
    max-width: none;
    height: 100vh;
    transform: none;
    visibility: visible;
    transition: transform .24s ease, visibility 0s;
  }
  .ap-side-head { justify-content: center; padding: var(--space-16) var(--space-8); }
  .ap-side-name { display: none; }
  .ap-side-foot { display: none; }
  .ap-navscrim { display: none; }
  .ap-burger { display: none; }
  .ap-nav { padding: var(--space-10) var(--space-8); align-items: center; }
  .ap-nav-item { justify-content: center; padding: var(--space-11) 0; }
  .ap-nav-label { display: none; }
  .ap-count {
    position: absolute;
    margin: calc(-1 * var(--space-16)) 0 0 var(--space-16);
  }

  .ap-main { padding: var(--space-20) var(--space-20) var(--space-48); }

  /* One line per booking from here up: the columns are what make a list of
     forty rows scannable instead of forty little cards. The shared row's 10px
     of vertical padding is tuned for one line of text; these cells carry two,
     so the row is already tall enough without it and loses eight pixels — over
     forty rows that is most of a screen. */
  .ap-row { padding-top: var(--space-6); padding-bottom: var(--space-6); }
  .ap-open {
    grid-template-columns: 96px minmax(0, 1.3fr) minmax(0, 1fr) auto auto;
    grid-template-areas: "when where who num state";
    gap: var(--space-12);
  }
  .ap-cell-state { justify-self: end; }
  .ap-line { grid-template-columns: 104px minmax(0, 1fr) auto minmax(0, 1.2fr); }
  .ap-line-note { grid-column: auto; }
  .ap-grid { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
  /* The room editor's fourteen fields pair up: building beside status, floor
     beside number, opening beside closing. */
  .ap-form-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* ============================================================
   Laptop and up — the rail becomes the full labelled sidebar.
   ============================================================ */

@media (min-width: 1024px) {
  .ap { grid-template-columns: 248px minmax(0, 1fr); }
  .ap-side-head { justify-content: flex-start; padding: var(--space-18) var(--space-16); }
  .ap-side-name { display: block; }
  .ap-side-foot { display: block; }
  .ap-nav { padding: var(--space-12); align-items: stretch; }
  .ap-nav-item { justify-content: flex-start; padding: var(--space-10) var(--space-12); }
  .ap-nav-label { display: block; }
  .ap-count { position: static; margin: 0; }

  .ap-main { padding: var(--space-20) var(--space-26) var(--space-48); }
  .ap-cols { grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); }
  .ap-open { grid-template-columns: 108px minmax(0, 1.3fr) minmax(0, 1fr) auto auto; }
  .ap-messenger { height: calc(100vh - 190px); }
}

/* ============================================================
   Small desktop — the row can afford a fourth text column, so the organiser's
   department stops sharing a line with their name and becomes its own cell.
   ============================================================ */

@media (min-width: 1280px) {
  .ap-open {
    grid-template-columns: 112px minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, .8fr) auto auto;
    grid-template-areas: "when where who dept num state";
  }
  .ap-cell-dept { grid-area: dept; display: block; }
  /* …and the copy that was sharing the name's line goes, which is the half of
     "stops sharing a line" that was never written. Only the stand-in copy
     carries this class; a real meeting title keeps its place. */
  .ap-cell-who .ap-who-dept { display: none; }
}

/* ============================================================
   Desktop — the working area stops growing and centres instead. 1920 is this
   same layout with wider gutters, not a fourth shape.
   ============================================================ */

@media (min-width: 1440px) {
  .ap-main {
    margin: 0 auto;
    padding: var(--space-26) var(--space-32) var(--space-48);
  }
  .ap-tiles { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .ap-open {
    grid-template-columns: 120px minmax(0, 1.3fr) minmax(0, 1fr) minmax(0, .8fr) auto auto;
  }
}

/* The floors of one building, inside its editor.

   Chips rather than rows: a floor is a number, and a number in a full-width row
   is a row that is mostly empty. They wrap because a tall building is a long
   list and a horizontal scroller inside a modal is a thing people do not find. */
.ap-floor-chips { display: flex; flex-wrap: wrap; gap: var(--space-8); }

.ap-floor-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-4) var(--space-4) var(--space-12);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text);
  font-size: var(--fs-13);
}

/* The chip's own class rather than `.ap-floor-chip .ds-btn--icon`: a design-system
   class belongs to one stylesheet, and reaching into it from here is how a button
   ends up with five sizes depending on which screen it is on. */
.ap-floor-x { width: 24px; height: 24px; min-height: 24px; }

/* The box and its button on one line: they are one action, and a button on its
   own line reads as a second one. */
.ap-floor-add { display: flex; gap: var(--space-8); margin-top: var(--space-8); }
.ap-floor-num { flex: 1 1 auto; min-width: 0; }

/* Possible duplicates: a review queue, not a directory.

   The pair is the unit, so it is a card - two names either side of an arrow,
   the reason it was offered under them, and the review folded away until
   somebody asks for it. Nothing here is styled to look decided: the merge is
   irreversible and the screen should feel like a question until the last click.

   The two accounts sit side by side on a wide screen and stack on a narrow one.
   Side by side is the whole point of the comparison, so the stacked case keeps
   the order - the account named first in the pair is the account named first
   below it - and never reflows the columns into a grid that reads across. */
.ap-dup-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
  margin-bottom: var(--space-12);
}
.ap-dup-lead { margin: 0; flex: 1 1 220px; min-width: 0; }

.ap-dups { display: grid; gap: var(--space-12); }

.ap-dup {
  border: 1px solid var(--border);
  border-radius: var(--radius-12);
  background: var(--surface);
  padding: var(--space-12);
}

.ap-dup__head {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  min-width: 0;
  font-size: var(--fs-14);
  color: var(--text);
}
.ap-dup__side { flex: 1 1 0; min-width: 0; }
.ap-dup__vs { flex: 0 0 auto; color: var(--text-faint); }

.ap-dup__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8);
  margin-top: var(--space-8);
}

.ap-dup__body { margin-top: var(--space-12); }

/* The warning reads before the choice, not after it: a name match is weak, and
   an administrator who learns that on the confirmation dialog has already
   decided. */
.ap-dup__warn {
  margin: 0 0 var(--space-8);
  padding: var(--space-8) var(--space-12);
  border: 1px solid var(--gold-line);
  border-radius: var(--radius-11);
  background: var(--gold-soft);
  color: var(--text);
  font-size: var(--fs-12-5);
}
.ap-dup__note {
  margin: 0 0 var(--space-12);
  color: var(--text-dim);
  font-size: var(--fs-12-5);
}

.ap-dup__cols { display: grid; gap: var(--space-12); }

.ap-dup__col {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  border: 1px solid var(--border);
  border-radius: var(--radius-11);
  background: var(--surface-2);
  padding: var(--space-12);
  min-width: 0;
}

.ap-dup__name { margin: 0; font-size: var(--fs-14); color: var(--text); }

.ap-dup__facts { margin: 0; display: grid; gap: var(--space-4); }
.ap-dup__fact { display: flex; gap: var(--space-8); min-width: 0; }
.ap-dup__fact dt {
  flex: 0 0 auto;
  color: var(--text-faint);
  font-size: var(--fs-11-5);
}
.ap-dup__fact dd {
  margin: 0;
  flex: 1 1 0;
  min-width: 0;
  color: var(--text);
  font-size: var(--fs-11-5);
  text-align: right;
}

.ap-dup__moves {
  margin: 0;
  color: var(--text-faint);
  font-size: var(--fs-11);
  text-transform: uppercase;
  letter-spacing: .04em;
}
.ap-dup__rows {
  margin: 0;
  padding-left: var(--space-16);
  color: var(--text);
  font-size: var(--fs-12-5);
}
.ap-dup__empty, .ap-dup__role {
  margin: 0;
  color: var(--text-dim);
  font-size: var(--fs-12-5);
}

/* The button sits at the bottom of whichever column is taller, so the two
   choices are always offered on the same line as each other. */
.ap-dup-keep { margin-top: auto; }

@media (min-width: 768px) {
  .ap-dup__cols { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The journal's action, in words. The code is in `title` rather than on screen,
   so the row reads as a sentence and the exact identifier is one hover away. */
.ap-action-said { color: var(--text); }

/* ---- the staff list: filters, identity marks, the merge badge -------------

   Chips rather than a <select>. Seven options where six are one word each, and
   the one that matters — «Требуют объединения» — has to be visible without
   opening anything: a filter hidden inside a dropdown is a filter nobody knows
   is there. They wrap to a second line on a narrow screen and never scroll
   sideways. */
.ap-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin: 0 0 var(--space-12);
}

.ap-chip {
  appearance: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-dim);
  padding: var(--space-5) var(--space-12);
  font: inherit;
  font-size: var(--fs-12-5);
  line-height: 1.2;
  cursor: pointer;
}
.ap-chip:hover { color: var(--text); }
.ap-chip.is-on {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--text);
}

/* The number beside «Требуют объединения». Hidden entirely at zero rather than
   drawn as «(0)»: a badge that is always there stops being a signal. */
.ap-badge-n {
  display: inline-block;
  min-width: var(--space-18);
  margin-left: var(--space-6);
  padding: 0 var(--space-5);
  border-radius: var(--radius-pill);
  background: var(--danger-soft);
  color: var(--text);
  font-size: var(--fs-11);
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* One mark per open door. A column of these is meant to be scanned down, so
   they are fixed width and monospaced-by-numbers rather than sized to their
   text. */
.ap-ids { display: inline-flex; gap: var(--space-4); }

.ap-id {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--space-24);
  height: var(--space-18);
  padding: 0 var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-6);
  background: var(--surface-2);
  color: var(--text-dim);
  font-size: var(--fs-11);
  line-height: 1;
}
.ap-id.is-on { border-color: var(--accent); color: var(--text); }
.ap-id.is-clash {
  border-color: var(--gold-line);
  background: var(--gold-soft);
  color: var(--text);
  font-weight: 600;
}

.ap-cell-quiet { color: var(--text-dim); white-space: nowrap; }

/* ---- one person, opened --------------------------------------------------

   The editor was a form. It is now a form followed by four sections that are
   not form fields at all — what this account can sign in with, when it was last
   used, what is in it, and what can be taken away. They read as blocks with a
   rule between them rather than as more inputs, because an administrator
   scanning for «why can this person not log in» must not have to read past a
   department field to find it. */
.ap-u-sec { margin-top: var(--space-16); }
.ap-u-sec + .ap-u-sec { border-top: 1px solid var(--border); padding-top: var(--space-16); }

.ap-u-kv {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-4) var(--space-12);
  font-size: var(--fs-12-5);
}
.ap-u-kv dt { color: var(--text-dim); min-width: 0; }
.ap-u-kv dd { margin: 0; color: var(--text); text-align: right; white-space: nowrap; }

/* Counters. Four small cards that wrap; on a phone they become two by two
   rather than a row that scrolls. */
.ap-u-cnt {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  gap: var(--space-8);
}
.ap-u-cnt__one {
  border: 1px solid var(--border);
  border-radius: var(--radius-11);
  padding: var(--space-8) var(--space-10);
  background: var(--surface);
  min-width: 0;
}
.ap-u-cnt__n {
  display: block;
  font-size: var(--fs-18);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.ap-u-cnt__k { display: block; font-size: var(--fs-11); color: var(--text-dim); }

/* One row per sign-in method: what it is, what it is connected to, and the one
   button that can take it away. */
.ap-u-id {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  padding: var(--space-8) 0;
  border-bottom: 1px solid var(--border);
  min-width: 0;
}
.ap-u-id:last-child { border-bottom: 0; }
.ap-u-id__main { flex: 1 1 0; min-width: 0; }
.ap-u-id__name { display: block; font-size: var(--fs-12-5); color: var(--text); }
.ap-u-id__sub {
  display: block;
  font-size: var(--fs-11);
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The Danger Zone announces itself. A red rule and a red heading, because the
   two buttons inside it are the only ones on the screen that cannot be undone
   by pressing something else. */
.ap-u-danger {
  margin-top: var(--space-16);
  border: 1px solid var(--danger);
  border-radius: var(--radius-12);
  padding: var(--space-12);
  background: var(--danger-soft);
}
.ap-u-danger .ap-block-title { color: var(--text); }
.ap-u-danger__row { display: grid; gap: var(--space-4); margin-top: var(--space-8); }
.ap-u-danger__hint { margin: 0; font-size: var(--fs-11); color: var(--text-dim); }

/* The merge warning at the top of an account that has an open conflict. It is
   the first thing on the screen because every other question about the account
   — why a login fails, why the bookings look short — has the same answer while
   it is there. */
.ap-u-clash {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
  margin: 0 0 var(--space-12);
  padding: var(--space-8) var(--space-12);
  border: 1px solid var(--gold-line);
  border-radius: var(--radius-11);
  background: var(--gold-soft);
  font-size: var(--fs-12-5);
  color: var(--text);
}
.ap-u-clash p { margin: 0; flex: 1 1 200px; min-width: 0; }

@media (max-width: 430px) {
  .ap-u-kv { grid-template-columns: minmax(0, 1fr); }
  .ap-u-kv dd { text-align: left; white-space: normal; }
}

/* The Danger Zone's summary is four lines, one per thing about to be destroyed.
   A paragraph that collapsed them onto one line would be a paragraph somebody
   skims past on the way to the field. */
.ap-prewrap { white-space: pre-wrap; text-align: left; }

/* A conflict card carries two facts and two buttons. On a phone the meta row
   wraps rather than squeezing the buttons into slivers — «Это разные люди» is
   not a control anybody should hit by accident. */
.ap-dup__meta { flex-wrap: wrap; }
.ap-dup__why { display: inline-flex; align-items: center; gap: var(--space-8); min-width: 0; }
.ap-dup__acts { display: inline-flex; align-items: center; gap: var(--space-6); }
.ap-dup__tries { font-size: var(--fs-11); color: var(--text-dim); white-space: nowrap; }
.ap-dup-second { margin-top: var(--space-24); }

/* The bulk bar appears only when something is ticked, above the table rather
   than floating over it: a bar that covers the last row is a bar that hides the
   thing somebody is about to select. */
.ap-bulk {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8);
  margin: 0 0 var(--space-8);
  padding: var(--space-8) var(--space-12);
  border: 1px solid var(--accent);
  border-radius: var(--radius-11);
  background: var(--accent-soft);
  font-size: var(--fs-12-5);
  color: var(--text);
}
.ap-bulk__n { font-variant-numeric: tabular-nums; }
.ap-bulk__acts { display: inline-flex; flex-wrap: wrap; gap: var(--space-6); }

/* Narrow and fixed, so ticking a column of boxes is a straight line down. */
.ap-pickcol { width: var(--space-40); text-align: center; }

/* --- administrators ------------------------------------------------------ */

/* One permission, one line: the name on the left, the three-state control on
   the right. Laid out deliberately rather than left to inline flow, which is
   what it was — the pair rendered, but only because a label and a select happen
   to sit next to each other, and nothing held them apart or kept them on one
   line at a readable width.

   `wrap` rather than a fixed split: at a phone's width the name takes the line
   and the control drops beneath it, which is better than either of them being
   squeezed to a few characters. */
.ap-acc-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8);
  padding: var(--space-6) 0;
  font-size: var(--fs-13);
  color: var(--text);
}
.ap-acc-row > span { flex: 1 1 auto; min-width: 0; }
.ap-acc-row select { flex: 0 1 auto; max-width: 100%; }

.ap-acc-block {
  margin: var(--space-12) 0 0;
  padding: var(--space-8) var(--space-12);
  border: 1px solid var(--border);
  border-radius: var(--radius-11);
}
.ap-acc-block > legend {
  padding: 0 var(--space-6);
  font-size: var(--fs-12-5);
  font-weight: 600;
  color: var(--text-dim);
}

/* --- room form: the sections added in 0020 ------------------------------- */

/* A heading rather than another grid row. The form asks fourteen questions and
   three of them are about different things entirely — what the room has, how
   the size rule treats it, and what somebody booking it is told. */
.ap-room-sec {
  margin: var(--space-16) 0 var(--space-8);
  font-size: var(--fs-12-5);
  font-weight: 600;
  color: var(--text-dim);
}
.ap-room-checks { display: flex; flex-wrap: wrap; gap: var(--space-12) var(--space-16); }
.ap-room-check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  font-size: var(--fs-13);
  color: var(--text);
}

/* --- the duty roster, under Settings -------------------------------------- */

/* One card per person: who she is, whether she is on duty, and her rooms
   grouped by building. The rooms are checkboxes rather than a multi-select
   because the question «which of these does she do» is answered by reading all
   of them, and a collapsed control hides the forty-first. */
.ap-duty-roster { display: grid; gap: var(--space-12); margin: var(--space-12) 0 0; }
.ap-rota-card { padding: var(--space-14); }
.ap-rota-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-10);
  margin: 0 0 var(--space-8);
}
.ap-rota-bld { margin: var(--space-10) 0 0; }
.ap-rota-bld-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8);
  margin: 0 0 var(--space-6);
}
/* Two columns where there is room for two, one where there is not. A fixed
   count would put a room name on two lines at a phone's width. */
.ap-rota-rooms {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--size-180), 1fr));
  gap: var(--space-6) var(--space-12);
}
.ap-rota-save { margin: var(--space-12) 0 0; }
.ap-rota-add { margin: var(--space-14) 0 0; }

/* The Coffee Lady group block: how to bind one, shown as the command to send.
   A <pre> because it is a command somebody types character for character, and a
   paragraph would let a font substitute a dash. */
.ap-cg-how { margin: var(--space-12) 0 var(--space-6); }
.ap-cg-why { margin: var(--space-8) 0 0; }
.ap-cg-cmd {
  margin: 0;
  padding: var(--space-10) var(--space-12);
  border: 1px solid var(--border);
  border-radius: var(--radius-8);
  background: var(--surface-2);
  font: var(--fs-13)/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  overflow-x: auto;
}

/* --- the room-size rule, under Settings ---------------------------------- */

.ap-set-block { margin: var(--space-20) 0 0; }
.ap-set-h { margin: 0 0 var(--space-4); font-size: var(--fs-14); color: var(--text); }
.ap-set-actions { margin: var(--space-12) 0 0; }

/* One rung of the ladder: two numbers and a way to remove it. Wraps rather
   than squeezing, because at a phone's width four controls in a row leave each
   of them too narrow to read. */
.ap-cap-tier {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
  margin: 0 0 var(--space-8);
}
/* Sized through a class of its own rather than by reaching into `.ds-input`:
   the design system owns that class, and a second stylesheet redefining it is
   how two files come to disagree about what an input looks like. */
.ap-cap-n { width: var(--space-64); }

/* The ready-made condition, beside its label rather than under it: it is one
   control, and a whole row for one button reads as a section. */
.ap-room-tpl{
  display:flex;align-items:center;gap:var(--space-10);flex-wrap:wrap;
  margin:0 0 var(--space-12)
}

/* A permission that is on whatever the editor says — the administrator
   baseline, or something another granted key requires. A word rather than a
   disabled select: a control that cannot be moved is still a control, and this
   is not one. */
.ap-acc-lock{
  flex:none;padding:var(--space-2) var(--space-9);border-radius:var(--radius-pill);
  background:var(--surface-2);color:var(--text-dim);
  font:var(--weight-620) var(--fs-11)/var(--lh-1-35) var(--font);white-space:nowrap
}

/* The people screen's filter row and its pager.

   The selects sit beside the search and wrap onto a second line when the row
   runs out — `.ap-toolbar` already wraps, so this only gives them a width worth
   wrapping at. */
.ap-ufilter { min-width: var(--size-180); flex: 0 1 auto; }

.ap-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-8);
  margin-bottom: var(--space-10);
}
.ap-pager__count {
  margin: 0 0 var(--space-10);
  font-size: var(--fs-12-5);
  color: var(--text-dim);
}
.ap-pager .ap-pager__count { margin-bottom: 0; }
