/* ─────────────────────────────────────────────────────────────────────────────
   People & sharing (renderer/admin/admin.js) — the third room of the Systems tab.

   Direction: THE KEEPER'S REGISTER — the same walnut-and-brass hand as the
   Collection storage ledger (renderer/storage/storage.css) and the Automations
   gauge wall (renderer/automations/automations.css), arranged as a hall where
   every person is a mounted plate with their face, their seal and their keys.
   It used to be the app's generic settings cards (grey slabs, a violet gradient
   button, a 940px column stranded in the middle of a 2000px screen), which is
   the one surface in the Systems tab that did not belong to its own tab.

   THE THREE RULES THIS ROOM IS BUILT ON
   1. No blank space. The pane is full-bleed, and every list is a FLEX wall whose
      plates share one basis — a short last row stretches to the edge instead of
      leaving dead cells. "Add someone" and "This device" are plates in that same
      wall, not stray cards below it, so the wall is never a lonely row.
   2. Congruent. One plate shape for a person, a code, an audit line and an
      action. One seal shape for a role. One button shape everywhere.
   3. A state is a painted seal AND a word, never a coloured dot and never
      colour alone — the room reads the same in a screenshot and read aloud.

   Icons are the painted set in renderer/admin/icons/ (forge, archived as icon
   collection `hearth-people`). The only drawn glyph is the back chevron.
   ───────────────────────────────────────────────────────────────────────────── */

:is(#ustore-pane-people, #pp-modal-host) {
  --pp-wood:      #171219;
  --pp-wood-2:    #1d1720;
  --pp-plate:     #221b24;
  --pp-plate-2:   #2a2029;
  --pp-brass:     #c9a24a;
  --pp-brass-dim: #8a6f33;
  --pp-ember:     #ff9a3c;
  --pp-ember-hi:  #ffc77a;
  --pp-ink:       var(--text);
  --pp-ink-dim:   var(--text-dim);
  --pp-ink-mute:  var(--text-mute);
  --pp-line:      rgba(255, 206, 160, .10);
  --pp-line-hi:   rgba(255, 190, 130, .28);
  --pp-good:      #cf9a3f;
  --pp-bad:       #e0543f;
  --pp-off:       #6b6152;
  --pp-serif: 'Iowan Old Style', 'Palatino Linotype', Palatino, 'Book Antiqua', Georgia, serif
}

/* The pane's own layout — NOT the popout host's, which is a bare positioned
   div on <body> that only wants the tokens above. */
#ustore-pane-people {
  /* Full bleed. The Storage room already pads .ustore-scroll; a max-width here
     is what left two hand-spans of dead wall on either side of the register. */
  max-width: none;
  margin: 0;
  /* A floor of its own. .ustore-scroll's own 56px is not enough on its own once
     the room grows past the viewport: the wall's last row then sits flush against
     the bottom edge of the screen (Rober, 2026-09-28: "just bleeds into edge"). */
  padding: 0 0 36px;
  min-width: 0;
  /* Fill the tab's height as well as its width. The room is a flex item of
     .ustore-scroll, so flex-grow hands it whatever height the lane bar leaves,
     and the chain below passes that down to the wall — a room with four plates
     in it should not stop half way down the screen and leave the wall bare.

     ⛔ It must GROW and never SHRINK, and it must not carry `min-height: 0`.
     .ustore-scroll is the scroller AND a flex column, so a shrinkable item with
     no automatic minimum gets compressed to the viewport: the room's box then
     ends above its own content, the scroller measures that short box, and the
     last shelf hangs past the bottom edge where scrolling cannot reach it —
     the page simply ends mid-panel with no floor (Rober, 2026-09-28: the
     Profiles shelf "cut off"). The earlier attempt to shrink the CONTENT until
     it fitted an 1100px window treated the symptom; this is the cause. */
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
}

.pp-room { flex: 1 0 auto; display: flex; flex-direction: column; }
/* The last shelf of a room is the one that grows; everything above it keeps its
   natural height so nothing important is stretched out of proportion. */
.pp-room > .pp-sec:last-child { flex: 1 0 auto; display: flex; flex-direction: column; }
.pp-room > .pp-sec:last-child > .pp-wall { flex: 1 1 auto; align-content: stretch; }
/* …but only so far. Three short plates on a tall screen would otherwise become
   three 500px voids with two lines in them, which is worse than the gap it was
   closing. The cap is desktop-only (a phone stacks one plate per row and is
   taller than the screen anyway) and is lifted for a plate that has grown
   content of its own, so it can never clip. */
@media (min-width: 721px) {
  .pp-room > .pp-sec:last-child > .pp-wall > * { max-height: 380px; }
  .pp-room > .pp-sec:last-child > .pp-wall > .is-picking { max-height: none; }
  /* Plates in a row share a height, so capping the OTHERS while one is uncapped
     left the row's neighbours short of the line they sit on. Lift the cap for
     the whole wall while any plate is open. */
  .pp-room > .pp-sec:last-child > .pp-wall:has(.is-picking) > * { max-height: none; }
}
#ustore-pane-people[hidden] { display: none; }
:is(#ustore-pane-people, #pp-modal-host) button { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }

/* ── the room's head ──────────────────────────────────────────────────────── */

.pp-head {
  display: flex;
  align-items: flex-end;
  gap: 20px;
  flex-wrap: wrap;
  padding-bottom: 16px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--pp-line);
}
.pp-head-text { flex: 1 1 320px; min-width: 0; }
.pp-title {
  margin: 0;
  font-family: var(--pp-serif);
  font-size: clamp(26px, 3.4vw, 38px);
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--pp-ink);
}
/* The answer to the question the room exists for, in words, at the size of the
   question — the same contract the Automations verdict keeps. */
.pp-verdict {
  margin: 6px 0 0;
  font-size: clamp(15px, 1.5vw, 18px);
  line-height: 1.45;
  color: var(--pp-brass);
  text-wrap: pretty;
}
.pp-head-actions { flex: 0 0 auto; display: flex; gap: 10px; align-items: center; }

/* ── the room rail: Accounts · Invite codes · Shared · Profiles ───────────────
   Flex with one shared basis, so four chips divide the wall exactly however wide
   it is. Deliberately a size DOWN from the Systems lane bar above it: that bar
   changes room, this one changes shelf inside a room. */

/* The rail is the ONLY way between the room's shelves, and one of them — Shared,
   "what is out of this house, and to whom" — is the thing an owner comes here to
   check. On a room taller than the window it used to scroll off the top, so from
   the bottom of Profiles there was no sharing anywhere on screen and no way back
   to it without scrolling blind (Rober, 2026-09-28: "where is the sharing hub?").
   It now rides the top edge of the Systems scroller instead. The backdrop is
   painted only while it is actually stuck (.is-stuck, set by admin.js): a band
   that is always opaque reads as a seam across the room at rest, since the wall
   behind it is the tab's own gradient and not a flat colour.

   Desktop only, on purpose: on a phone the five chips wrap to three rows, and
   pinning that takes a quarter of the screen for navigation nobody is using
   while they read. There the rail scrolls with the room, as it did. */
/* The marker admin.js measures the rail against to know it has lifted off (see
   markRailStuck). Zero-height and out of the flow's way: it must not add a pixel
   of its own or the rail would sit ten pixels lower than it was drawn. */
.pp-rail-anchor { height: 0; margin: 0; padding: 0; }
.pp-rail {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 20px;
}
@media (min-width: 721px) {
  .pp-rail {
    margin-top: -10px;
    padding: 10px 0;
    position: sticky;
    top: 0;
    z-index: 3;
  }
}
/* The band is drawn by ::before and bled past the room's box on every side: the
   padding .ustore-scroll keeps around the room is outside the room's own box, so
   a band the size of the rail alone would let a plate slide past beside it and
   through the strip above it. The scroller clips whatever overshoots. */
.pp-rail.is-stuck::before {
  content: '';
  position: absolute;
  inset: -30px -30px 0;
  z-index: -1;
  background: linear-gradient(180deg, rgba(14, 11, 17, .96), rgba(14, 11, 17, .88));
  backdrop-filter: blur(12px) saturate(130%);
  -webkit-backdrop-filter: blur(12px) saturate(130%);
  box-shadow: 0 10px 22px -16px #000, 0 1px 0 var(--pp-line);
}
@media (prefers-reduced-transparency: reduce) {
  .pp-rail.is-stuck::before {
    background: #0e0b11;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}
.pp-chip {
  flex: 1 1 190px;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 11px;
  min-height: 60px;
  padding: 9px 14px;
  border-radius: 12px;
  border: 1px solid var(--pp-line);
  background: linear-gradient(180deg, var(--pp-plate-2), var(--pp-plate));
  color: var(--pp-ink-dim);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.pp-chip img { width: 34px; height: 34px; flex: none; }
.pp-chip-text { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.pp-chip-name { font-size: 14px; font-weight: 600; white-space: nowrap; }
.pp-chip-n {
  font-size: 11.5px;
  color: var(--pp-ink-mute);
  font-variant-numeric: tabular-nums;
}
.pp-chip:hover { border-color: var(--pp-brass-dim); color: var(--pp-ink); }
.pp-chip:focus-visible { outline: 2px solid var(--pp-ember); outline-offset: 2px; }
.pp-chip.is-active {
  color: var(--pp-ink);
  border-color: rgba(255, 154, 60, .5);
  background:
    radial-gradient(220px 60px at 10% 0%, rgba(255, 154, 60, .16), transparent 70%),
    linear-gradient(180deg, #3a2517, #2a1a12);
  box-shadow: inset 0 1px 0 rgba(255, 220, 180, .12);
}
.pp-chip.is-active .pp-chip-n { color: var(--pp-ember-hi); }

/* ── the hero band: who YOU are, and the room's figures ───────────────────── */

.pp-hero {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
  margin: 0 0 18px;
  padding: 18px 22px;
  border-radius: 16px;
  border: 1px solid rgba(201, 162, 74, .22);
  background:
    radial-gradient(700px 240px at 6% 0%, rgba(255, 154, 60, .12), transparent 68%),
    linear-gradient(180deg, rgba(255, 244, 222, .045), rgba(0, 0, 0, .2)),
    var(--pp-wood);
  box-shadow: inset 0 1px 0 rgba(255, 238, 205, .07), 0 18px 40px rgba(0, 0, 0, .3);
}
.pp-hero-face { position: relative; flex: none; }
/* Its OWN class, not `.pp-hero-face img`: that would also match the seal below
   it at equal specificity and win on order, blowing the 34px badge up to a
   bordered 78px disc that covered the whole portrait. */
.pp-hero-img {
  display: block;
  width: 78px; height: 78px;
  border-radius: 50%;
  object-fit: cover;
  background: #14161f;
  border: 2px solid var(--pp-brass-dim);
  box-shadow: 0 0 0 4px rgba(0, 0, 0, .35), 0 8px 22px rgba(0, 0, 0, .45);
}
/* The seal hangs off the portrait the way a badge hangs off a coat. */
.pp-hero-seal {
  position: absolute;
  right: -8px; bottom: -6px;
  width: 36px; height: 36px;
  filter: drop-shadow(0 3px 5px rgba(0, 0, 0, .6));
}
.pp-hero-text { flex: 1 1 260px; min-width: 0; }
.pp-hero-name {
  margin: 0;
  font-family: var(--pp-serif);
  font-size: 23px;
  font-weight: 600;
  color: #f3e9d6;
  overflow-wrap: anywhere;
}
.pp-hero-sub { margin: 4px 0 0; font-size: 13.5px; color: var(--pp-ink-dim); line-height: 1.5; }

/* Three figures, equal cells, so the band's right half is never a dead gutter. */
.pp-figs {
  flex: 1 1 320px;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
/* The same strip is reused as a shelf's header. Inside a growing section it is a
   flex ITEM, and `.pp-figs`'s own flex-grow (which it needs inside the hero) made
   it swallow the slack meant for the wall — 340px of empty rules under four
   numbers. State the size it actually wants here. */
.pp-sec > .pp-figs { flex: 0 0 auto; margin-bottom: 18px; }
.pp-figs.is-four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
/* The same strip again, small, inside an audit plate: it says which libraries the
   count is made of, which is the question the plate would otherwise leave open. */
.pp-figs-mini { flex: 0 0 auto; gap: 8px; margin-top: 12px; }
.pp-figs-mini .pp-fig { padding-left: 9px; }
.pp-figs-mini .pp-fig-v { font-size: 19px; }
.pp-figs-mini .pp-fig-k { font-size: 9.5px; letter-spacing: .08em; margin-top: 2px; }
.pp-fig { min-width: 0; padding-left: 14px; border-left: 2px solid rgba(201, 162, 74, .3); }
.pp-fig:first-child { border-left-color: var(--pp-brass); }
.pp-fig-v {
  display: block;
  font-family: var(--pp-serif);
  font-size: clamp(21px, 2.4vw, 28px);
  font-weight: 700;
  line-height: 1.1;
  color: #f3e9d6;
  font-variant-numeric: tabular-nums;
}
.pp-fig-k {
  display: block;
  margin-top: 4px;
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pp-ink-mute);
}

/* ── the wall ─────────────────────────────────────────────────────────────────
   FLEX, not grid, precisely so a short last row stretches to the full width
   rather than leaving dead cells. Every plate shares one basis, so they keep the
   same shape whatever is in them. */

.pp-wall {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: stretch;
}
.pp-plate {
  flex: 1 1 340px;
  min-width: 280px;
  /* No max-height: a cap here would CLIP, because a plate can grow content of
     its own (the inline face picker). The body absorbs whatever slack the wall
     hands down, which parks the action rail on the plate's foot — where a rail
     belongs — instead of leaving the bottom of the screen bare. */
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 15px 16px 13px;
  border-radius: 14px;
  border: 1px solid var(--pp-line);
  background: linear-gradient(180deg, var(--pp-plate-2), var(--pp-plate));
  box-shadow: inset 0 1px 0 rgba(255, 238, 205, .05), 0 10px 26px rgba(0, 0, 0, .28);
}
/* A plate that carries a whole panel of controls (a new invite, the share
   editor's sections) takes the full width instead of pretending to be a card. */
.pp-plate.is-wide { flex: 1 1 100%; }
.pp-plate.is-off { opacity: .62; }
.pp-plate.is-you { border-color: rgba(201, 162, 74, .34); }

.pp-plate-head { display: flex; align-items: center; gap: 12px; min-width: 0; }
.pp-plate-face {
  width: 46px; height: 46px; flex: none;
  border-radius: 50%;
  object-fit: cover;
  background: #14161f;
  border: 2px solid var(--pp-brass-dim);
}
/* A plate with no person on it still opens with an icon, so every plate in the
   wall starts on the same line. */
.pp-plate-ico { width: 46px; height: 46px; flex: none; }
.pp-plate-title { min-width: 0; flex: 1 1 auto; }
.pp-plate-name {
  margin: 0;
  font-family: var(--pp-serif);
  font-size: 18px;
  font-weight: 600;
  color: #f3e9d6;
  line-height: 1.25;
  overflow-wrap: anywhere;
}
.pp-plate-meta {
  margin: 2px 0 0;
  font-size: 12px;
  color: var(--pp-ink-mute);
  overflow-wrap: anywhere;
}
.pp-plate-body { flex: 1 1 auto; min-width: 0; }
.pp-line {
  display: flex; gap: 8px;
  font-size: 13px; line-height: 1.5;
  color: var(--pp-ink-dim);
  overflow-wrap: anywhere;
}
.pp-line + .pp-line { margin-top: 5px; }
.pp-line-k {
  flex: none;
  min-width: 52px;
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--pp-ink-mute);
  padding-top: 2px;
}
.pp-line-v { min-width: 0; }
.pp-line-v.is-quiet { color: var(--pp-ink-mute); }

/* Your password and your shape code sit above their own buttons rather than in
   the run of facts, so each reads as a thing you can act on. The rule above them
   separates them from the face grid — and from each other — without adding
   another boxed panel inside the plate. They are one shape on purpose: they are
   the same question asked twice ("how do I get in?"). */
.pp-email,
.pp-pass,
.pp-shape {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--pp-line);
}

/* A zero-height mark left in the flow where the rail's place would be, so the
   sticky rail can be compared against it (see markRailStuck). It is measured,
   never seen. */
.pp-rail-anchor { height: 0; margin: 0; padding: 0; }

/* ── the role seal: a painted mark AND the word, never one without the other ── */

.pp-seal {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  padding: 3px 10px 3px 4px;
  border-radius: 999px;
  border: 1px solid var(--pp-line-hi);
  background: rgba(0, 0, 0, .28);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--pp-ink-dim);
  white-space: nowrap;
}
.pp-seal img { width: 20px; height: 20px; flex: none; }
.pp-seal.is-owner { color: var(--pp-ember-hi); border-color: rgba(255, 154, 60, .45); }
.pp-seal.is-admin { color: var(--pp-brass); border-color: rgba(201, 162, 74, .45); }
.pp-seal.is-off   { color: var(--pp-off); }

/* A small stated fact with no icon — "whole library", "revoked", "3 uses left". */
.pp-tag {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--pp-line-hi);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--pp-ink-mute);
  white-space: nowrap;
}
.pp-tag.is-good { color: var(--pp-good); border-color: rgba(207, 154, 63, .5); }
.pp-tag.is-bad  { color: var(--pp-bad);  border-color: rgba(224, 84, 63, .5); }
.pp-tags { display: flex; flex-wrap: wrap; gap: 6px; }

/* ── buttons: ONE shape, everywhere in this room ──────────────────────────── */
/* The pane still reuses a few of the app's generic classes (.btn from
   styles.css), and .btn.primary there is a violet-to-cyan gradient with a purple
   glow — the one thing in this tab that belongs to no room. Rebound here rather
   than renamed in the markup, so nothing can reintroduce it by copying a line. */

:is(#ustore-pane-people, #pp-modal-host) .pp-btn,
:is(#ustore-pane-people, #pp-modal-host) .btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;            /* touch target */
  padding: 8px 14px;
  border-radius: 10px;
  border: 1px solid var(--pp-line-hi);
  background: linear-gradient(180deg, var(--pp-plate-2), var(--pp-plate));
  color: var(--pp-ink);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  box-shadow: none;
  cursor: pointer;
  text-decoration: none;          /* the Published shelf's "Open" is an <a> */
  transition: border-color .15s ease, background .15s ease;
}
:is(#ustore-pane-people, #pp-modal-host) .pp-btn:hover,
:is(#ustore-pane-people, #pp-modal-host) .btn:hover {
  border-color: var(--pp-brass-dim);
  background: linear-gradient(180deg, #33272f, var(--pp-plate-2));
}
:is(#ustore-pane-people, #pp-modal-host) .pp-btn:active,
:is(#ustore-pane-people, #pp-modal-host) .btn:active { transform: translateY(1px); }
:is(#ustore-pane-people, #pp-modal-host) .pp-btn:disabled,
:is(#ustore-pane-people, #pp-modal-host) .btn:disabled { opacity: .5; cursor: default; transform: none; }
:is(#ustore-pane-people, #pp-modal-host) .pp-btn:focus-visible,
:is(#ustore-pane-people, #pp-modal-host) .btn:focus-visible { outline: 2px solid var(--pp-ember); outline-offset: 2px; }
:is(#ustore-pane-people, #pp-modal-host) .pp-btn.is-primary,
:is(#ustore-pane-people, #pp-modal-host) .btn.primary {
  border-color: rgba(255, 154, 60, .5);
  background: linear-gradient(180deg, #3a2517, #2a1a12);
  color: var(--pp-ember-hi);
}
:is(#ustore-pane-people, #pp-modal-host) .pp-btn.is-primary:hover,
:is(#ustore-pane-people, #pp-modal-host) .btn.primary:hover {
  filter: none;
  background: linear-gradient(180deg, #4a2e1c, #341f14);
  border-color: rgba(255, 154, 60, .75);
}
:is(#ustore-pane-people, #pp-modal-host) .pp-btn.is-danger:hover {
  border-color: rgba(224, 84, 63, .6);
  color: #ffbfae;
  background: linear-gradient(180deg, #33201f, #291a1b);
}
:is(#ustore-pane-people, #pp-modal-host) .pp-btn.is-small,
:is(#ustore-pane-people, #pp-modal-host) .btn.small { min-height: 34px; padding: 5px 11px; font-size: 12.5px; }
:is(#ustore-pane-people, #pp-modal-host) .pp-btn img { width: 20px; height: 20px; flex: none; }

/* The action rail at a plate's foot: buttons wrap and the row fills. */
.pp-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  align-items: center;
  padding-top: 11px;
  border-top: 1px solid var(--pp-line);
}
.pp-acts.is-bare { border-top: 0; padding-top: 0; }
/* Buttons share the row rather than leaving a gutter — but a lone button on a
   stretched plate must not become a 1200px slab, hence the cap. */
.pp-acts .pp-btn { flex: 1 1 auto; max-width: 300px; justify-content: center; }
.pp-msg {
  flex: 1 1 100%;
  font-size: 12.5px;
  min-height: 16px;
  color: var(--pp-ink-mute);
  overflow-wrap: anywhere;
}
.pp-msg.is-bad { color: var(--pp-bad); }
.pp-msg.is-good { color: var(--pp-good); }

/* ── forms ───────────────────────────────────────────────────────────────── */

.pp-form { display: grid; gap: 10px; }
.pp-pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 10px; }
:is(#ustore-pane-people, #pp-modal-host) .pp-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 0;
  min-width: 0;
}
.pp-field > span {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pp-ink-mute);
}
/* `search` is here too: the popout's two pickers use type=search for the phone
   keyboard's Search key and the native clear affordance, and without it they fall
   back to a white browser slab on the walnut. */
:is(#ustore-pane-people, #pp-modal-host) input[type=text],
:is(#ustore-pane-people, #pp-modal-host) input[type=search],
:is(#ustore-pane-people, #pp-modal-host) input[type=password],
:is(#ustore-pane-people, #pp-modal-host) input[type=number],
:is(#ustore-pane-people, #pp-modal-host) select {
  width: 100%;
  /* 16px so iOS does not zoom the page when the field takes focus. */
  font: 16px/1.4 inherit;
  padding: 9px 12px;
  min-height: 44px;
  background: rgba(0, 0, 0, .3);
  border: 1px solid var(--pp-line-hi);
  border-radius: 10px;
  color: var(--pp-ink);
}
:is(#ustore-pane-people, #pp-modal-host) input[type=search]::-webkit-search-cancel-button { filter: invert(1) opacity(.5); }
:is(#ustore-pane-people, #pp-modal-host) input::placeholder { color: var(--pp-ink-mute); }

/* A native checkbox paints a white slab on this wall, and `accent-color` only
   colours it once it is CHECKED — so every unticked shelf was a bright square.
   Drawn here instead: a brass-rimmed well that fills with ember when ticked. The
   tick is two borders, which is geometry, not a picture. */
/* ⚠ Radios are styled by the SAME rules as checkboxes: this room's pickers are
   single-choice (which shelf, who gets it), and a native radio is a blue dot on
   walnut. Only the shape and the mark differ, below. */
:is(#ustore-pane-people, #pp-modal-host) :is(input[type=checkbox], input[type=radio]) {
  appearance: none;
  -webkit-appearance: none;
  width: 19px; height: 19px;
  flex: none;
  margin: 0;
  border-radius: 6px;
  border: 1px solid var(--pp-line-hi);
  background: rgba(0, 0, 0, .45);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .5);
  display: inline-grid;
  place-content: center;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}
:is(#ustore-pane-people, #pp-modal-host) :is(input[type=checkbox], input[type=radio])::before {
  content: '';
  width: 9px; height: 5px;
  border-left: 2px solid #201203;
  border-bottom: 2px solid #201203;
  transform: rotate(-45deg) translate(1px, -2px);
  opacity: 0;
}
:is(#ustore-pane-people, #pp-modal-host) :is(input[type=checkbox], input[type=radio]):hover { border-color: var(--pp-brass-dim); }
:is(#ustore-pane-people, #pp-modal-host) :is(input[type=checkbox], input[type=radio]):checked {
  border-color: var(--pp-ember);
  background: linear-gradient(180deg, var(--pp-ember-hi), var(--pp-ember));
}
:is(#ustore-pane-people, #pp-modal-host) :is(input[type=checkbox], input[type=radio]):checked::before { opacity: 1; }
:is(#ustore-pane-people, #pp-modal-host) :is(input[type=checkbox], input[type=radio]):disabled { opacity: .45; cursor: default; }
:is(#ustore-pane-people, #pp-modal-host) :is(input[type=checkbox], input[type=radio]):focus-visible { outline: 2px solid var(--pp-ember); outline-offset: 2px; }
:is(#ustore-pane-people, #pp-modal-host) input:focus,
:is(#ustore-pane-people, #pp-modal-host) select:focus { outline: none; border-color: var(--pp-brass); }
:is(#ustore-pane-people, #pp-modal-host) input:focus-visible,
:is(#ustore-pane-people, #pp-modal-host) select:focus-visible { outline: 2px solid var(--pp-ember); outline-offset: 1px; }

/* ── an invite code: meant to be read aloud, copied, and sent ─────────────── */

.pp-code {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 7px 13px;
  border-radius: 10px;
  border: 1px solid var(--pp-line-hi);
  background: rgba(0, 0, 0, .3);
  color: #f3e9d6;
  font: 700 16px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .14em;
  cursor: pointer;
}
.pp-code:hover { border-color: var(--pp-brass); color: var(--pp-ember-hi); }
.pp-code:focus-visible { outline: 2px solid var(--pp-ember); outline-offset: 2px; }

/* ── empty states: a painted plate, never an empty box ───────────────────── */

.pp-empty {
  flex: 1 1 340px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
  padding: 26px 20px;
  border-radius: 14px;
  border: 1px dashed var(--pp-line-hi);
  background:
    radial-gradient(360px 180px at 50% 34%, rgba(255, 154, 60, .09), transparent 70%),
    linear-gradient(180deg, rgba(255, 244, 222, .022), rgba(0, 0, 0, .18));
  color: var(--pp-ink-mute);
  font-size: 13px;
  line-height: 1.5;
}
.pp-empty img { width: 96px; height: 96px; opacity: .9; }
/* An empty state stretches like any other cell, including when it stands alone.
   It was briefly pinned to `align-self: start` to stop the Profiles shelf running
   off the fold — but the fold was the flex-shrink fault fixed at the top of this
   file, and pinning it left a shelf-deep band of dead wall under the Shared room
   instead (no blank space, vertically either). Growing only ever takes the room
   that is already free, so it can no longer push anything past the bottom. */
.pp-empty b { display: block; color: var(--pp-ink-dim); font-size: 14px; font-weight: 600; }

/* ── section heads inside a room ─────────────────────────────────────────── */

.pp-sec { margin: 0 0 18px; }
.pp-sec:last-child { margin-bottom: 0; }
.pp-sec-head { display: flex; align-items: center; gap: 12px; margin: 0 0 11px; }
.pp-sec-head h2 {
  margin: 0;
  font-family: var(--pp-serif);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--pp-brass);
}
.pp-sec-head::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(90deg, var(--pp-line-hi), transparent);
}
.pp-sec-note { flex: none; font-size: 12px; color: var(--pp-ink-mute); }

/* ── the sort bar ─────────────────────────────────────────────────────────────
   Every shelf here is a list that grows, so every one of them carries the same
   bar in its section head — one shape, one place, whatever you are looking at.
   It sits AFTER the head's rule (order: 2 beats the ::after's implicit 0) so the
   rule still runs from the title and the pills park against the right wall. */
.pp-sort {
  order: 2;
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.pp-sort-ico { width: 18px; height: 18px; opacity: .75; flex: none; }
/* .pp-acts caps its buttons at 300px and stretches them; a sort pill is neither,
   so the shared .pp-btn sizing is overridden back to the label's own width. */
.pp-sort .pp-btn { flex: 0 0 auto; max-width: none; min-height: 30px; padding: 4px 10px; font-size: 12px; }
@media (max-width: 720px) {
  /* The note is already hidden at this width; the bar takes the whole next line
     rather than squeezing the title, and its pills divide it evenly. */
  .pp-sec-head { flex-wrap: wrap; }
  .pp-sort { flex: 1 1 100%; justify-content: flex-start; }
  .pp-sort .pp-btn { flex: 1 1 auto; min-height: 44px; font-size: 13px; }
}

/* ── the share editor ────────────────────────────────────────────────────── */

.pp-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
  padding: 8px 2px;
  min-height: 40px;
  background: none;
  border: 0;
  color: var(--pp-ink-dim);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.pp-back:hover { color: var(--pp-ember-hi); }
.pp-back:focus-visible { outline: 2px solid var(--pp-ember); outline-offset: 2px; border-radius: 8px; }
/* The room's only drawn glyph — a chevron is simple enough to be geometry. */
.pp-back::before {
  content: '';
  width: 8px; height: 8px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}
.pp-lead {
  margin: 0 0 12px;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--pp-ink-dim);
  max-width: 78ch;
}

/* Rule tiles. One shape, one size — and FLEX rather than a grid, because there
   are nine presets and an auto-fit grid left the last row as two tiles beside
   five empty cells. Flex lines stretch. */
.pp-rules { display: flex; flex-wrap: wrap; gap: 9px; }
/* These tiles are <label>s, and `.settings-card label` / `.modal-panel label` in
   styles.css set flex-direction:column at specificity (0,2,0) — a bare class
   loses to that and every checkbox stacks ABOVE its label, centred. Match the
   weight and state the direction. (Kept from the first build of this panel: the
   trap is in the shared stylesheet, not in this file.) */
:is(#ustore-pane-people, #pp-modal-host) .pp-rule {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  flex: 1 1 200px;
  min-width: 0;
  margin: 0;
  padding: 11px 13px;
  min-height: 52px;
  border-radius: 11px;
  border: 1px solid var(--pp-line);
  background: rgba(0, 0, 0, .22);
  font-size: 13px;
  color: var(--pp-ink-dim);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, color .15s ease;
}
.pp-rule:hover { border-color: var(--pp-brass-dim); color: var(--pp-ink); }
.pp-rule:focus-within { border-color: var(--pp-brass); }
.pp-rule.is-on {
  color: var(--pp-ink);
  border-color: rgba(255, 154, 60, .5);
  background: linear-gradient(180deg, rgba(255, 154, 60, .13), rgba(255, 154, 60, .04));
}
.pp-rule-label { min-width: 0; font-size: 13px; font-weight: 600; }
.pp-rule-label small { display: block; font-weight: 400; color: var(--pp-ink-mute); font-size: 11px; }

/* ── the publish picker's shelves ─────────────────────────────────────────────
   Grouped, because "Games → Local", "Cosy Nights" and "The whole library" are
   three different KINDS of thing to publish and a flat list of twelve identical
   rows says none of that. Each carries the painted mark its lane wears elsewhere
   in the app, so the picker is scanned by shape rather than read. */
.pp-shelf-head {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 14px 0 7px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--pp-brass);
}
.pp-shelf-head:first-child { margin-top: 2px; }
.pp-shelf-n {
  font-size: 10.5px;
  letter-spacing: .04em;
  color: var(--pp-ink-mute);
  padding: 1px 7px;
  border-radius: 999px;
  border: 1px solid var(--pp-line);
}
/* The rule line, then the group's sort pills against the right wall — the same
   shape as a section head's, so the two read as one idiom. */
.pp-shelf-head::after {
  content: '';
  order: 8;
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(90deg, var(--pp-line-hi), transparent);
}
.pp-shelf-sort { order: 9; display: flex; gap: 6px; flex: none; }
.pp-shelf-sort .pp-btn { min-height: 28px; padding: 3px 9px; font-size: 11.5px; letter-spacing: 0; text-transform: none; }
/* A shelf's own face — a collection's first cover, a series' first poster. It
   falls back to the lane's painted mark, which is drawn smaller and centred
   (`is-mark`) because a mark is a glyph and a cover is a picture. */
.pp-shelf-art {
  width: 44px; height: 30px; flex: none;
  border-radius: 6px;
  object-fit: cover;
  background: rgba(0, 0, 0, .35);
  border: 1px solid var(--pp-line);
}
.pp-shelf-art.is-mark { object-fit: contain; padding: 3px; border-color: transparent; background: none; }
.pp-picker-list.is-shelves { padding: 0 12px 12px; }

/* ── the share popout ─────────────────────────────────────────────────────────
   Invite codes, Shared and Published were three shelves for one question, so
   they are one Sharing shelf whose every action opens this. A modal rather than
   a page because the wall behind it should stay exactly where it was. */
.pp-modal {
  position: fixed;
  inset: 0;
  /* Above the topbar (z 9999 in topbar.css), not merely above the room. A dialog
     that slides UNDER the app's own chrome loses its title and its close button
     to it — which is exactly what happened on a phone, where the panel is
     full-bleed and its header landed behind the Hearth bar. */
  z-index: 10100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(10px, 3vh, 40px) clamp(10px, 3vw, 40px);
}
.pp-modal-scrim {
  position: absolute;
  inset: 0;
  background: rgba(6, 4, 8, .66);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.pp-modal-panel {
  position: relative;
  width: min(880px, 100%);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-radius: 16px;
  border: 1px solid rgba(201, 162, 74, .3);
  background: linear-gradient(180deg, var(--pp-plate-2), var(--pp-wood));
  box-shadow: inset 0 1px 0 rgba(255, 238, 205, .08), 0 40px 90px rgba(0, 0, 0, .7);
}
.pp-modal-head {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--pp-line);
}
.pp-modal-head .pp-title { font-size: 22px; }
.pp-modal-x { margin-left: auto; min-width: 44px; justify-content: center; }
/* The body is the only thing that scrolls, so the title and the action stay put
   however long the shelf list gets. */
.pp-modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 14px 18px;
  display: grid;
  gap: 14px;
}
.pp-modal-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 18px 14px;
  border-top: 1px solid var(--pp-line);
}
.pp-modal-foot .pp-msg { flex: 1 1 200px; min-height: 0; }
@media (max-width: 620px) {
  .pp-modal { padding: 0; }
  .pp-modal-panel { width: 100%; height: 100%; max-height: none; border-radius: 0; border: 0; }
  .pp-modal-foot .pp-btn { flex: 1 1 auto; justify-content: center; }
}

/* ── the Sharing shelf ────────────────────────────────────────────────────── */
.pp-grant-filters { margin: 0 0 12px; gap: 8px; }
.pp-grant-filters .pp-btn { flex: 0 0 auto; max-width: none; gap: 8px; }
.pp-grant-q {
  flex: 1 1 240px;
  min-width: 0;
  min-height: 44px;
  padding: 8px 13px;
  border-radius: 10px;
  border: 1px solid var(--pp-line);
  background: rgba(0, 0, 0, .3);
  color: var(--pp-ink);
  font: inherit;
  font-size: 13.5px;
}
.pp-grant-q::placeholder { color: var(--pp-ink-mute); }
.pp-grant-q:focus { outline: none; border-color: var(--pp-brass); }
/* "Share something…" is a PLATE, the same shape as the grants beside it — the
   room's congruence rule: a set of like things is one shape, and the new-thing
   action is a peer, not a stray button above the wall. */
.pp-plate-new {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 14px;
  cursor: pointer;
  border-style: dashed;
}
.pp-plate-new:hover { border-color: var(--pp-brass-dim); background: rgba(255, 154, 60, .06); }
.pp-plate-new:focus-visible { outline: 2px solid var(--pp-ember); outline-offset: 2px; }
.pp-plate-new .pp-plate-name { color: var(--pp-ember-hi); }

/* The item picker: a search field over a scrolling list of library items. */
.pp-picker { display: grid; gap: 9px; }
.pp-picker-list {
  max-height: 440px;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--pp-line);
  border-radius: 11px;
  background: rgba(0, 0, 0, .3);
}
:is(#ustore-pane-people, #pp-modal-host) .pp-item {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 11px;
  margin: 0;
  padding: 9px 13px;
  min-height: 44px;
  font-size: 13px;
  color: var(--pp-ink-dim);
  border-bottom: 1px solid var(--pp-line);
  cursor: pointer;
}
.pp-item:last-child { border-bottom: 0; }
.pp-item:hover, .pp-item:focus-within { background: rgba(255, 206, 160, .05); color: var(--pp-ink); }
.pp-item-title { min-width: 0; flex: 1; overflow-wrap: anywhere; }
.pp-item-via { font-size: 11px; color: var(--pp-ink-mute); white-space: nowrap; flex: none; }

/* The publish picker's rows are BUTTONS, not checkboxes: publishing is choosing
   one thing, and a row of ticks would invite "publish these nine" — which is the
   one shape this feature deliberately does not have. */
:is(#ustore-pane-people, #pp-modal-host) .pp-pub-row {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--pp-line);
  border-radius: 0;
  background: none;
  text-align: left;
  font: inherit;
  font-size: 13px;
  color: var(--pp-ink-dim);
}
.pp-pub-row.is-on {
  color: var(--pp-ember-hi);
  background: linear-gradient(90deg, rgba(255, 154, 60, .16), rgba(255, 154, 60, .03));
  box-shadow: inset 3px 0 0 var(--pp-ember);
}
.pp-pub-row:focus-visible { outline: 2px solid var(--pp-ember); outline-offset: -2px; }

/* The item's own picture in the row and on the plate: choosing one thing out of a
   few thousand should be a glance, not a read. A 3:2 crop matches the library's
   own tiles. `.is-mark` is the no-art fallback — the lane's painted mark, sized
   down and contained so it does not pretend to be a poster. */
.pp-pub-art, .pp-plate-art {
  flex: none;
  border-radius: 6px;
  object-fit: cover;
  background: rgba(0, 0, 0, .35);
  border: 1px solid var(--pp-line);
}
.pp-pub-art { width: 52px; height: 34px; }
.pp-plate-art { width: 64px; height: 42px; border-radius: 8px; }
.pp-pub-art.is-mark, .pp-plate-art.is-mark {
  object-fit: contain;
  padding: 4px;
  opacity: .8;
  border-color: transparent;
}

/* A public URL is meant to be read, checked and copied, so it is set like the
   invite code — but it is long, so it wraps rather than overflowing its plate. */
.pp-link {
  font-size: 12.5px;
  letter-spacing: .02em;
  min-height: 34px;
  padding: 5px 10px;
  text-align: left;
  word-break: break-all;
  white-space: normal;
}

/* ── profiles: the cast ──────────────────────────────────────────────────── */
/* FIXED column counts, not auto-fit: there are eight characters, and auto-fit
   picked six across at this width, leaving a ragged 6 + 2 last row. 8, 4 and 2
   all divide 8, so the grid always fills. */
.pp-faces-wrap { display: grid; gap: 10px; margin-top: 4px; }
.pp-faces {
  display: grid;
  grid-template-columns: repeat(8, minmax(0, 1fr));
  gap: 10px;
}
@media (max-width: 1180px) { .pp-faces { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .pp-faces { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* The SAME picker, opened inline on somebody's plate, is in a ~340px column, so
   it sizes off the plate and not off the viewport — eight fixed columns there
   would be 40px cells under 72px portraits. Four, because 4 divides 8 exactly
   and the two rows therefore both fill. (`.is-wide` is the full-width plate your
   own profile sits on; it keeps the eight-across grid.) */
.pp-plate:not(.is-wide) .pp-faces { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 7px; }
.pp-plate:not(.is-wide) .pp-face { padding: 9px 4px 8px; font-size: 10px; gap: 6px; }
.pp-plate:not(.is-wide) .pp-face img { width: 46px; height: 46px; }
@media (max-width: 560px) { .pp-plate:not(.is-wide) .pp-faces { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pp-face {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 13px 8px 11px;
  border-radius: 12px;
  border: 1px solid var(--pp-line);
  background: rgba(0, 0, 0, .22);
  color: var(--pp-ink-mute);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  text-align: center;
  line-height: 1.25;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease, color .15s ease, transform .15s ease;
}
.pp-face img {
  width: 64px; height: 64px;
  border-radius: 50%;
  object-fit: cover;
  background: #14161f;
  border: 2px solid transparent;
}
.pp-face:hover { border-color: var(--pp-brass-dim); color: var(--pp-ink-dim); transform: translateY(-2px); }
.pp-face:focus-visible { outline: 2px solid var(--pp-ember); outline-offset: 2px; }
.pp-face.is-on {
  color: var(--pp-ember-hi);
  border-color: rgba(255, 154, 60, .55);
  background: linear-gradient(180deg, rgba(255, 154, 60, .14), rgba(255, 154, 60, .04));
}
.pp-face.is-on img { border-color: var(--pp-brass); }

/* The owner's own face: ONE WIDE TILE above the cast, not a ninth cell — nine
   tiles cannot fill a grid that divides by eight, and it is not one of the eight.
   Written with both classes and kept AFTER .pp-face, because everything it
   changes (the column layout, the 72px portrait, the centred text) is stated
   there at the same specificity and would otherwise win on order. */
.pp-face.pp-face-solo {
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 16px;
  padding: 12px 18px;
  text-align: left;
  border-color: rgba(255, 154, 60, .3);
  background:
    radial-gradient(520px 140px at 6% 50%, rgba(255, 154, 60, .13), transparent 70%),
    rgba(0, 0, 0, .28);
}
.pp-face.pp-face-solo img { width: 72px; height: 72px; }
.pp-face-solo-text { text-align: left; font-size: 12px; font-weight: 400; line-height: 1.5; }
.pp-face-solo-text b {
  display: block;
  font-family: var(--pp-serif);
  font-size: 19px;
  font-weight: 600;
  color: #f3e9d6;
  margin-bottom: 2px;
}
.pp-face.pp-face-solo.is-on { border-color: rgba(255, 154, 60, .6); }
.pp-face.pp-face-solo.is-on .pp-face-solo-text b { color: var(--pp-ember-hi); }

/* ── phone ───────────────────────────────────────────────────────────────── */

@media (max-width: 720px) {
  .pp-hero { padding: 16px; gap: 14px; }
  .pp-figs { flex-basis: 100%; }
  .pp-plate { min-width: 0; flex-basis: 100%; }
  .pp-chip { flex-basis: calc(50% - 5px); }
}
@media (max-width: 560px) {
  /* The shelf's supporting note does not fit beside its own heading at this
     width, and wrapping it broke the heading over two lines. It repeats what the
     shelf shows, so it goes rather than wraps. */
  .pp-sec-head h2 { flex: 0 0 auto; }
  .pp-sec-note { display: none; }
  .pp-figs.is-four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 420px) {
  .pp-figs { gap: 8px; }
  .pp-fig { padding-left: 9px; }
}

@media (prefers-reduced-motion: reduce) {
  :is(#ustore-pane-people, #pp-modal-host) * { transition: none !important; }
  .pp-face:hover { transform: none; }
}

/* ── guest chrome ────────────────────────────────────────────────────────────
   A guest's session is refused these channels by the server regardless; hiding
   the controls means they are not invited to click something that will only
   answer "not shared with this account". Driven by one attribute on <body> that
   admin.js sets from users:me. */
body[data-hearth-role="member"] .owner-only { display: none !important; }

/* The wide popout — the share editor, whose item picker genuinely needs the room. */
.pp-modal-panel.is-wide { width: min(1120px, 100%); }
/* The room must not scroll under the finger while a dialog is open. The Systems
   scroller is the thing that actually scrolls, so it is the thing to pin. */
.ustore-scroll.is-modal-open { overflow: hidden; }

/* A radio is the same stone as a checkbox, round, with a lit centre instead of a
   tick — so a "pick one" reads as pick-one at a glance. */
:is(#ustore-pane-people, #pp-modal-host) input[type=radio] { border-radius: 50%; }
:is(#ustore-pane-people, #pp-modal-host) input[type=radio]::before {
  width: 9px; height: 9px;
  border: 0;
  border-radius: 50%;
  transform: none;
  background: #201203;
}
