/* APPS / PROCESSES / WEBSITES — scalable policy workspace.
 *
 * Wide screens get stable scan-down columns.  At phone widths the very same
 * cells become labelled, full-width cards; no table is horizontally scrolled
 * or squeezed below the 320px iPhone SE target.  State colours always retain
 * words and icons, so colour is never the only signal.
 */

.pw-icon {
  width: 17px;
  height: 17px;
  flex: 0 0 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.pw-icon-label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 0;
}

.pw-toolbar {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(150px, .45fr) minmax(150px, .45fr);
  gap: 10px;
  align-items: end;
  margin: 16px 0 12px;
}

.pw-tool {
  display: grid;
  gap: 5px;
  min-width: 0;
}

.pw-tool-label {
  color: var(--haze);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
}

.pw-input-shell {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 0;
}

.pw-input-shell > .pw-icon {
  position: absolute;
  left: 12px;
  z-index: 1;
  color: var(--haze);
  pointer-events: none;
}

.pw-input-shell > .inp {
  width: 100%;
  min-width: 0;
  padding-left: 38px;
}

.pw-table-head {
  display: grid;
  align-items: center;
  gap: 14px;
  padding: 0 14px 8px;
  color: var(--haze);
  font-size: 11px;
  font-weight: 750;
  letter-spacing: .07em;
  text-transform: uppercase;
}

.pw-row {
  display: grid;
  align-items: center;
  gap: 14px;
  min-width: 0;
  margin: 0;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: color-mix(in srgb, var(--dusk2) 72%, transparent);
}

.pw-row + .pw-row { margin-top: 8px; }

/* 44px AND NOT 42px IN THE LAST TRACK, AND THE 2px COMES OUT OF THE LIMIT
 * COLUMN SO THE TOTAL DOES NOT MOVE. `.pw-more-button` is declared `width:
 * 44px` — squarely, because it is an icon button with no text and its WIDTH is
 * its smaller dimension against the touch target. A 42px track around a 44px
 * button put 2px of that button outside its own cell at every table width,
 * measured in en, es and fr and in both themes. Taking the 2px from the
 * limit column keeps the sum of the minimums at 769px, which is the number the
 * 1249px breakpoint below is derived from.
 *
 * THE ACCESS TRACK IS 270px, NOT 220. GoodQA follow-up, MEASURED at 1280x900
 * (one of the two required desktop widths) before this change: with the
 * grid's own "find the size of an fr" algorithm pinning this track to its
 * bare 220px floor at that width (its base/flex ratio, 220/1.5=146.7, sits
 * just above the free-space-per-fr the other five tracks left it, so it got
 * NONE of the leftover and the other tracks absorbed all of it — see the
 * commit that raised this number for the worked arithmetic), "Autoriser" and
 * "Bloquear" — the fr/es Allow and Block labels, not even the English ones —
 * had nowhere to fit on one line. 270px is measured from the real rendered
 * button width these three words need with their icon (now stacked above
 * the label rather than beside it, see `.pw-state-option > .pw-icon-label`
 * above) in all three shipped languages at this width, plus a working
 * margin so normal font-hinting jitter cannot reopen this — not a round
 * number picked by eye. */
.pw-process-head,
.pw-process-row {
  grid-template-columns:
    minmax(130px, 1.25fr)
    minmax(100px, .8fr)
    minmax(135px, 1.15fr)
    minmax(270px, 1.5fr)
    minmax(90px, .7fr)
    44px;
}

/* APPS' ACCESS TRACK IS 270px TOO, NOT 220 — the same defect as Processes'
 * above, on the same shared `.pw-state-option`/`.pw-icon-label` control with
 * the SAME three words ("Allow"/"Temporary"/"Block" and their es/fr
 * equivalents — `inv.actionAllow` etc. are the identical strings
 * `proc.actionAllow` etc. use), just not yet carried to this row when
 * Processes' fix shipped. MEASURED (not assumed identical) at 1100-1280px, in
 * en/es/fr: at the old 220px floor, "Autoriser" and "Bloquear" broke mid-word
 * ("Aut" over "oriser", "Bloque" over "ar") the same way Processes' did.
 * Bisecting the real rendered button width, the true minimum is 250px
 * (fr "Bloquer" is the tightest fit at 240px, es "Bloquear" needs 250px); 270
 * matches Processes' own number and keeps the same font-hinting-jitter margin
 * for a shared control that must not reopen this per-language.
 *
 * UNLIKE PROCESSES, THIS ROW DOES NOT NEED `height: auto`. MEASURED at 270px,
 * every language/width combination in the sweep above: icon-on-its-own-line
 * plus one line of text is 35px of content (17px icon + 1px row-gap + 17px
 * text line) inside the existing 44px fixed `.pw-state-option` box, a good
 * 9px of margin — because at 270px the label never splits to two lines here,
 * so the three-line stack (icon / word-line-1 / word-line-2) that forced
 * Processes' fix never happens on Apps. If a future translation needs a
 * fourth track width or a narrower one, re-measure this rather than copying
 * Processes' `height: auto` in by assumption. */
.pw-app-head,
#invList .pw-app-row {
  grid-template-columns:
    minmax(120px, 1fr)
    minmax(125px, 1.1fr)
    minmax(100px, .8fr)
    minmax(270px, 1.45fr)
    minmax(94px, .72fr)
    42px;
}

/* SIX CHILDREN, AND ONLY FIVE TRACKS WERE DECLARED.
 *
 * `siteWorkspaceRow` appends the host avatar FIRST and then the five cells
 * this list names, so every website row had an implicit sixth column the grid
 * never sized. MEASURED at 1440 and at 3840, en/light, before this change: the
 * workspace overflowed its own box by 35-50px on every tab — the avatar's
 * 34px plus one 14px gap, pushed off the end — and the column labels above the
 * rows were one cell out of line with the cells they name. The avatar is now
 * the first declared track and `.pw-site-head` gets a matching empty leading
 * cell (see renderSitesWorkspace), so the two agree by construction.
 *
 * The content minimums come down with it. 170+190+150+150+42 plus four gaps
 * and the row's own padding demanded 786px of content width, which is more
 * than a 900px laptop has beside the 250px rail. */
.pw-site-head,
#sitesList .pw-site-row {
  grid-template-columns:
    34px
    minmax(140px, 1.1fr)
    minmax(160px, 1.25fr)
    minmax(120px, .9fr)
    minmax(140px, .9fr)
    44px;
}

#sites .pw-sites-panels { display: block; }
#sites .pw-site-panel { min-width: 0; }
#sites .pw-site-panel[hidden] { display: none; }
#sites .pw-site-panel > .rulehead { margin-bottom: 10px; }
#sitesList .pw-site-row > .favicon { align-self: start; margin-top: 2px; }
#sitesList .pw-site-row > .pw-state-cell {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  align-items: center;
}

#invList .pw-app-head { margin: 10px 0 0; }
#invList .invBacklogRows > .pw-app-head { margin-top: 0; }

/* The prior wide-screen card grid put two unrelated app rows beside each
 * other.  Stable columns need the full card width so values scan vertically. */
@media (min-width: 1200px) {
  #invList .invDecisionGroup { display: block; }
  #invList .invDecisionGroup > .pw-row,
  #invList .invDecisionGroup > .invMoreGroup { margin-top: 8px; }
  #invList .invMoreRows,
  #invList .invBacklogRows { grid-template-columns: minmax(0, 1fr); }
}

.pw-cell { min-width: 0; }

/* ---------------------------------------------------------------------------
   LONG TEXT WRAPS OR ELLIPSIZES INSIDE ITS OWN CELL, NEVER OVER A NEIGHBOUR.
   ---------------------------------------------------------------------------
   `min-width: 0` above only lets a TRACK shrink. It says nothing about what
   the ink inside the track does when the string is longer than the track is
   wide, and three strings on these rows routinely are:

     * A CATEGORY. `.pill` is `white-space: nowrap` everywhere on this
       dashboard, which is right for "Safe" and "3 waiting" and wrong for a
       classifier category. MEASURED at 1440x900, en: "Games & entertainment"
       is 146px of unbreakable text and it was drawn 46px outside its own cell
       and 32px into the description sentence beside it. Scoped to
       `.pw-rating-stack` so the nowrap every short pill relies on is
       untouched.

     * AN ACCESS LABEL. `.pw-state-option` is a 44px-tall button whose width is
       a share of one grid track, and its `.pw-icon-label` is an inline-flex
       that does not shrink: at 1440 the "Block" button was 22px wide and its
       label was drawn from 1306 to 1336, i.e. 14px past the button and across
       "View limits" in the next cell. `Voir les limites` and `Temporaire` make
       fr worse than en, which is why the ellipsis is here and not in a
       language-specific override.

     * A DAILY-LIMIT LABEL, for the same reason and in the same shape.

   The clamp is on the LABEL, not on the button: `.sw::before` (app.css) is the
   invisible hit-area expander that keeps these controls at their touch size,
   and `overflow: hidden` on the button itself would clip it. What the label
   then DOES with a string too long for it — wrap, and only clip as a last
   resort — is the rule below, with the measurement that chose it.
   --------------------------------------------------------------------------- */
.pw-rating-stack .pill {
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
}

.pw-state-option > .pw-icon-label,
.pw-limit-button > .pw-icon-label {
  max-width: 100%;
  overflow: hidden;
}

/* THE ICON WRAPS AWAY FROM THE WORD, NOT THE WORD'S OWN LETTERS.
 *
 * GoodQA follow-up, MEASURED at 1280x900 (one of the two required desktop
 * widths) before this change: "Allow" drew as "All" over "ow", "Temporary"
 * as "Tempora" over "ry", "Block" as "Bl" over "ock" — `overflow-wrap:
 * anywhere` below doing exactly what it is for (this control has no space or
 * hyphen to break at, so a box too narrow for the whole word breaks the word
 * itself) on a box that was too narrow because the icon was sharing the
 * line with it. The icon is 17px plus this file's 6px `.pw-icon-label` gap —
 * 23px this button was spending on something that is not the word a parent
 * is reading.
 *
 * Letting the icon wrap onto its own line ABOVE the label — same technique,
 * inside the same 44px-tall auto-height button `.pw-process-row .pw-state-
 * option { height: auto }` already relies on — gives the word that 23px
 * back without widening a single grid track. MEASURED after, at 1280x900
 * and 1440x900, in all three shipped languages (en/es/fr): every one of
 * Allow/Temporary/Block and their es/fr equivalents now draws on ONE line,
 * `overflow-wrap: anywhere` never triggers for real content, and it stays in
 * the rule below purely as the last-resort seatbelt this file already
 * documents for a future, longer translation — never the everyday case. */
.pw-state-option > .pw-icon-label {
  flex-wrap: wrap;
  row-gap: 1px;
}

.pw-state-option > .pw-icon-label > .pw-icon-label-text {
  text-align: center;
}

/* WRAP FIRST, ELLIPSIS ONLY AS THE LAST RESORT. An earlier version of this
 * rule was `white-space: nowrap` + `text-overflow: ellipsis`, which did stop
 * the overlap and cost too much to keep: MEASURED at 1440x900 in fr, the
 * access control's own labels lost 25px of "Autoriser" and 16px of "Bloquer",
 * i.e. about half of each word, on the one control that decides whether a
 * child can open an app. Wrapping keeps the whole word — this file's opening
 * contract is that these controls "always retain words and icons, so colour is
 * never the only signal", and half a word is not the word. `anywhere` because
 * these labels are single words with nothing to break at, `overflow: hidden`
 * so a label that STILL does not fit is clipped inside its own button instead
 * of painted across the cell beside it, and the ellipsis stays for the
 * one-line case a future longer string might hit. */
.pw-icon-label-text {
  min-width: 0;
  overflow: hidden;
  overflow-wrap: anywhere;
  text-overflow: ellipsis;
}

.pw-main-name {
  display: block;
  color: var(--cloud);
  font-size: 14px;
  font-weight: 720;
  overflow-wrap: anywhere;
}

.pw-secondary,
.pw-description {
  display: block;
  color: var(--haze);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.pw-row-detail { display: none; }

/* app.css has an older, deliberately high-specificity rule that turns every
 * `.asub` below an inventory name into inline prose.  These are the same facts
 * now represented by stable columns, so keep their row-only copies out of both
 * layout and the accessibility tree.  Without the matching specificity they
 * reappear at 320px and physically paint through the controls below them. */
#invList .pw-app-row .pw-main-cell > .pw-row-detail { display: none; }

#invList .pw-app-row .atime {
  width: auto;
  min-width: 0;
  max-width: none;
  margin-top: 4px;
  color: var(--haze);
  text-align: left;
}

.pw-description { margin-top: 3px; }

/* A FIXED 44px HEIGHT CLIPS A WRAPPED LABEL, so on this row the 44px is a
 * FLOOR and not a ceiling. `.pw-state-option` declares `height: 44px` as well
 * as `min-height: 44px`; with the wrap above, "Bloquear" and "Autoriser" take
 * two lines in the narrowest table columns and MEASURED 22px of that second
 * line was cut off at 1199-1280 in es and fr. `height: auto` keeps the floor
 * (the touch target is `min-height`, untouched), and the three segments stay
 * the same height as each other because `.pw-state-control` is a grid and its
 * items stretch. Scoped to the process row: Apps and Websites size these same
 * segments from wider tracks and neither was measured clipping. */
.pw-process-row .pw-state-option,
.pw-process-row .pw-state-control .sw { height: auto; }

/* `max-width` IS WHAT MAKES `max-content` SAFE. Without it the Close now
 * button is exactly as wide as its own label wants to be, whatever the name
 * column happens to be — and "Fermer maintenant" is 13px wider than that
 * column at 1200px, so the button hung out of the cell it lives in. Capped, it
 * wraps its label instead, which is the behaviour every other control on the
 * row already has. */
.pw-process-row .proc-close {
  display: block;
  width: max-content;
  max-width: 100%;
  margin-top: 8px;
}

.pw-meta-stack,
.pw-rating-stack {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.pw-meta-stack .pill,
.pw-rating-stack .pill { margin: 0; }

.pw-state-control {
  display: grid;
  grid-template-columns: minmax(0, .85fr) minmax(0, 1.3fr) minmax(0, .85fr);
  gap: 3px;
  min-width: 0;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--dusk2);
}

/* 44px, NOT 36. Every control in this file is one a parent taps on a phone,
 * and the acceptance size for this product is a 320px screen. At 36px the
 * three-way Allow / Temporary / Block choice was under the 44px touch target
 * on every one of the five validated widths — and it is the control that
 * decides whether a child can open an app, so a mis-tap here is a wrong
 * policy, not a cosmetic annoyance. The row is a flex/grid column on a phone
 * and a wide grid on a desktop; neither reflows from 8 more pixels. Raised
 * here rather than inside a phone-only media query on purpose: a pointer:fine
 * exemption would leave the two touch-first sizes (768 tablet portrait and a
 * 440 phone in landscape) reading as "desktop" on hardware that is neither. */
.pw-state-option,
.pw-state-control .sw {
  position: relative;
  width: auto;
  min-width: 0;
  height: 44px;
  min-height: 44px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--haze);
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  cursor: pointer;
}

/* `.sw` remains the legacy test/action hook on Apps and Processes, but its
 * old one-knob painting is not a second state language inside this control. */
.pw-state-control .sw::after { display: none; }
.pw-state-control .sw::before { inset: -4px; }

.pw-state-option:hover:not(:disabled),
.pw-state-option:focus-visible {
  border-color: var(--track);
  color: var(--cloud);
}

.pw-state-option.is-selected.pw-state-allowed {
  border-color: var(--mint);
  background: color-mix(in srgb, var(--mint) 13%, transparent);
  color: var(--done-text);
}

.pw-state-option.is-selected.pw-state-blocked {
  border-color: var(--coral);
  background: color-mix(in srgb, var(--coral) 13%, transparent);
  color: var(--coral);
}

.pw-state-option.is-selected.pw-state-temporary {
  border-color: var(--amber);
  background: color-mix(in srgb, var(--amber) 13%, transparent);
  color: var(--waiting-text);
}

.pw-state-option.is-unavailable {
  border-style: dashed;
  opacity: .76;
}

.pw-state-option:disabled { cursor: wait; opacity: .58; }

:root[data-theme="light"] .pw-state-option.is-selected.pw-state-allowed,
:root:not([data-theme]) .pw-state-option.is-selected.pw-state-allowed { color: #0A6242; }
:root[data-theme="light"] .pw-state-option.is-selected.pw-state-blocked,
:root:not([data-theme]) .pw-state-option.is-selected.pw-state-blocked { color: #B23127; }
:root[data-theme="light"] .pw-state-option.is-selected.pw-state-temporary,
:root:not([data-theme]) .pw-state-option.is-selected.pw-state-temporary { color: #7A4F00; }

.pw-limit-button,
.pw-more-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: 100%;
  min-height: 44px;
  padding: 7px 9px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: transparent;
  color: var(--cloud);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

/* Square, and both sides count. This one is an icon button with no text, so
 * its WIDTH was the smaller of its two dimensions at 40px — the reason a
 * height-only sweep would have called it compliant. */
.pw-more-button {
  width: 44px;
  padding: 0;
  color: var(--haze);
}

.pw-limit-button:hover,
.pw-limit-button:focus-visible,
.pw-more-button:hover,
.pw-more-button:focus-visible {
  border-color: var(--glow);
  color: var(--glow);
}

.pw-pager {
  display: grid;
  grid-template-columns: minmax(88px, auto) 1fr minmax(88px, auto);
  gap: 10px;
  align-items: center;
  margin-top: 14px;
}

.pw-page-button { min-height: 44px; }

/* THE CONTROLS THESE ROWS BORROW FROM app.css, BROUGHT UP TO THE SAME TARGET.
 *
 * `.iconbtn` (the Websites row's Remove) and `.ghost.small` (Review, Close
 * now, Open limit settings) are shared classes defined in app.css at roughly
 * 34px tall — below the 44px touch target on every width this workspace is
 * validated at. They are scoped HERE, to the workspace rows, rather than
 * raised in app.css: that file is shared with a dozen screens other lanes are
 * editing, and a global size change is not this lane's to make. Remove in
 * particular is DESTRUCTIVE and sits beside a decision pill, which is the
 * worst place on the screen to have to aim. */
.pw-row .iconbtn,
.pw-row .ghost.small,
.pw-detail-device .ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
}

.pw-page-status {
  color: var(--haze);
  font-size: 12px;
  text-align: center;
}

.pw-tabs {
  display: flex;
  gap: 6px;
  margin: 12px 0;
  padding: 4px;
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: 13px;
  background: var(--dusk2);
  scrollbar-width: thin;
}

.pw-tab {
  flex: 1 0 auto;
  min-height: 44px;
  padding: 8px 12px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: var(--haze);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
}

.pw-tab[aria-selected="true"] {
  border-color: var(--glow);
  background: color-mix(in srgb, var(--glow) 12%, transparent);
  color: var(--glow);
}

:root[data-theme="light"] .pw-tab[aria-selected="true"],
:root:not([data-theme]) .pw-tab[aria-selected="true"] { color: #075F82; }

.pw-dialog {
  width: min(680px, calc(100vw - 28px));
  max-height: min(780px, calc(100dvh - 28px));
  margin: auto;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--dusk);
  color: var(--cloud);
  box-shadow: var(--shadow);
}

.pw-dialog::backdrop { background: rgba(3, 9, 18, .72); backdrop-filter: blur(4px); }
/* Same lighter scrim app.css already gives .stepup-dialog::backdrop and
 * .schedule-dialog::backdrop in light theme -- this dialog's own light
 * override was missing, so a light-toggled parent got the dark-theme .72
 * near-opaque scrim behind an otherwise correctly light dialog box (visually
 * read as "the whole screen went dark"). Confirmed by screenshot 2026-09-19. */
:root[data-theme="light"] .pw-dialog::backdrop,
:root:not([data-theme]) .pw-dialog::backdrop { background: rgba(10,16,32,.42); }

.pw-dialog-shell {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  max-height: min(780px, calc(100dvh - 28px));
}

.pw-dialog-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 18px 14px;
  border-bottom: 1px solid var(--line);
}

.pw-dialog-eyebrow {
  margin-bottom: 3px;
  color: var(--glow);
  font-size: 11px;
  font-weight: 750;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.pw-dialog-title { font-size: clamp(19px, 4vw, 25px); line-height: 1.2; }

.pw-dialog-close {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  flex: 0 0 42px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--dusk2);
  color: var(--haze);
}

.pw-dialog-close:hover,
.pw-dialog-close:focus-visible { border-color: var(--glow); color: var(--glow); }

.pw-dialog-body {
  display: grid;
  gap: 12px;
  padding: 16px 18px 22px;
  overflow: auto;
  overscroll-behavior: contain;
}

.pw-detail-section {
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: 13px;
  background: var(--dusk2);
}

.pw-detail-heading {
  margin-bottom: 5px;
  color: var(--cloud);
  font-size: 13px;
}

.pw-detail-copy { color: var(--haze); font-size: 13px; overflow-wrap: anywhere; }

.pw-detail-list { display: grid; gap: 8px; }

.pw-detail-device {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
}

.pw-detail-device:last-child { border-bottom: 0; padding-bottom: 0; }
.pw-detail-device:first-child { padding-top: 0; }

.pw-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 18px max(12px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--dusk);
}

/* THE TABLE NEEDS ROOM IT DOES NOT HAVE UNTIL ~1100px, AND 760 WAS NOT WHERE
 * THAT HAPPENS.
 *
 * MEASURED on the Websites workspace, all four tabs, before this change:
 *
 *   768px   the PAGE scrolled sideways by 87-192px, in every theme and every
 *           language (fr worst, because its longer decision words widen the
 *           columns that were already over budget)
 *   1440px  no page scroll, but the workspace still overflowed its own box
 *
 * The arithmetic behind it: these rows are five or six columns whose minimums
 * total ~700px, plus four 14px gaps and 28px of row padding — about 786px of
 * CONTENT. app.css keeps the phone navigation shell until 899.98px and then
 * hands the page a 250px rail, so the content column is roughly 768px at a
 * 768px tablet and roughly 600px at a 900px laptop. The table has never fitted
 * either. It first fits at about 1100px, and that is where this now switches.
 *
 * BELOW IT IS NOT A CRUSHED TABLE, it is the card stack these rules build —
 * the same one the 320px phone gets. A cramped six-column table on a 1024px
 * laptop is not more information than five readable stacked facts; it is the
 * same information, unreadable, next to a horizontal scrollbar.
 *
 * policy-workspace-ui.test.js pins this number. If it moves again, move it
 * there in the same commit and say what was measured. */
@media (max-width: 1099.98px) {
  .pw-toolbar { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .pw-search-tool { grid-column: 1 / -1; }
  .pw-table-head { display: none; }

  .pw-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 12px;
    padding: 14px;
    align-items: start;
  }

  /* Processes carry more independent facts than the older four-column row.
   * Leaving them in the generic two-track phone grid lets auto-placement put
   * Rating, description, limit and More into narrow side-by-side cells.  Use
   * the same deliberate card stack as Apps, with More anchored to the title
   * row, so every value has a full 320px-phone content track. */
  .arow.procrow.pw-process-row {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  .arow.procrow.pw-process-row > .pw-cell {
    width: 100%;
    min-width: 0;
  }
  .arow.procrow.pw-process-row > .pw-main-cell { padding-right: 52px; }
  .arow.procrow.pw-process-row > .pw-more-cell {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 44px;
  }
  .arow.procrow.pw-process-row > .pw-state-cell,
  .arow.procrow.pw-process-row > .pw-limit-cell { align-self: stretch; }
  /* Inventory has years of legacy `.arow` phone-grid rules in apps.css. A
   * flex column here deliberately removes those named grid areas instead of
   * trying to win six separate cascade fights. The result is one predictable
   * full-width card at the 320px acceptance size: identity, description,
   * rating, access, then limit. The More action is pinned to the identity row
   * and never consumes a narrow text column. */
  #invList .pw-app-row {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding: 14px;
  }
  #invList .pw-app-row > .pw-cell {
    width: 100%;
    min-width: 0;
  }
  #invList .pw-app-row > .pw-main-cell { padding-right: 52px; }
  #invList .pw-app-row > .pw-more-cell {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 44px;
  }
  #invList .pw-app-row > .pw-state-cell,
  #invList .pw-app-row > .pw-limit-cell { align-self: stretch; }

  #sitesList .pw-site-row {
    grid-template-columns: 34px minmax(0, 1fr) 44px;
    grid-template-areas:
      "avatar main more"
      "description description description"
      "rating rating rating"
      "state state state";
  }
  #sitesList .pw-site-row > .favicon { grid-area: avatar; }
  #sitesList .pw-site-row > .pw-main-cell { grid-area: main; }
  #sitesList .pw-site-row > .pw-description-cell { grid-area: description; }
  #sitesList .pw-site-row > .pw-rating-cell { grid-area: rating; }
  #sitesList .pw-site-row > .pw-state-cell { grid-area: state; }
  #sitesList .pw-site-row > .pw-more-cell { grid-area: more; }

  .pw-main-cell,
  .pw-state-cell { grid-column: 1 / -1; }

  .pw-cell:not(.pw-main-cell):not(.pw-state-cell)::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 4px;
    color: var(--haze);
    font-size: 10px;
    font-weight: 750;
    letter-spacing: .06em;
    text-transform: uppercase;
  }

  .pw-more-cell { align-self: end; }
}

/* PROCESSES KEEPS THE CARD STACK LONGER THAN THE OTHER TWO WORKSPACES, AND
 * THE NUMBER IS ARITHMETIC RATHER THAN TASTE.
 *
 * The block above switches every workspace back to a table at 1100px. That was
 * derived for the five-column Websites row, and for Processes it was never
 * TESTED, because until app.css stopped claiming `.arow.procrow.pw-process-row`
 * the six-track template above had not actually been in force at any width —
 * the legacy five-track table was (see the `:not(.pw-process-row)` comment in
 * app.css). With the real tracks applied, this row's own declared minimums are
 *
 *     130 + 100 + 135 + 270 + 90 + 44          = 769px of tracks
 *     + 5 gaps x 14px                          =  70px
 *     + the row's 14px padding x2 and 1px border x2  =  30px
 *                                              = 869px of ROW
 *
 * (270, not the 220 an earlier pass of this file used — raised in the same
 * GoodQA follow-up that put the ACCESS TRACK comment above this block; the
 * 819px/1199px this comment used to say were the numbers before that.)
 *
 * and MEASURED on this dashboard the process card is `viewport - 380px` wide
 * wherever the family rail is up (L315/R1215 at 1280, L315/R1375 at 1440), so
 * the table first fits at 1249px. Asked for it at 1100 the page scrolled
 * sideways by 18px and the access control had 46px to draw three words in.
 *
 * SO THE STACK, NOT A SQUEEZED TABLE — the argument the block above already
 * makes, applied to the row it is actually true of. These are the same five
 * rules that block sets, restated for the 1100-1248.98 band rather than
 * widened in place, so nothing about Apps, Websites or any width below 1100
 * moves by a pixel; the per-cell labels come with them, because a stacked card
 * whose values have lost their column headings is not readable either. This
 * also means BOTH of the product's required desktop widths — 1280 as well as
 * 1440 — now sit above this table's own breakpoint again, which is what the
 * 270px access track was sized to guarantee rather than assume.
 *
 * If this number moves, move it with a measurement: the row width at the new
 * breakpoint, and the sum of the track minimums it has to hold.
 * platform/test/processes-layout.test.js measures 1024/1100/1200/1280 for
 * exactly this reason. */
@media (min-width: 1100px) and (max-width: 1248.98px) {
  .pw-process-head { display: none; }

  .arow.procrow.pw-process-row {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  .arow.procrow.pw-process-row > .pw-cell {
    width: 100%;
    min-width: 0;
  }
  .arow.procrow.pw-process-row > .pw-main-cell { padding-right: 52px; }
  .arow.procrow.pw-process-row > .pw-more-cell {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 44px;
  }
  .arow.procrow.pw-process-row > .pw-state-cell,
  .arow.procrow.pw-process-row > .pw-limit-cell { align-self: stretch; }

  .arow.procrow.pw-process-row > .pw-cell:not(.pw-main-cell):not(.pw-state-cell)::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 4px;
    color: var(--haze);
    font-size: 10px;
    font-weight: 750;
    letter-spacing: .06em;
    text-transform: uppercase;
  }
}

/* APPS NEEDS THE SAME TREATMENT AS PROCESSES ABOVE — the 220->270 access-track
 * fix (see the comment above `.pw-app-head` / `#invList .pw-app-row`) grew
 * this row's own declared minimums too:
 *
 *     120 + 125 + 100 + 270 + 94 + 42          = 751px of tracks
 *     + 5 gaps x 14px                          =  70px
 *     + the row's 14px padding x2 and 1px border x2  =  30px
 *                                              = 851px of ROW
 *
 * A FIRST PASS OF THIS FIX MEASURED ONLY `document.documentElement.
 * scrollWidth` AND SHIPPED 1139.98px — wrong. Page-level scroll is not the
 * only failure mode: a grid container whose own content-box is narrower than
 * its tracks' combined floor does not grow the page, it lets its LAST track
 * paint past the container's own border box (nothing here clips — no
 * ancestor in this row's chain sets `overflow` off `visible`), and outer
 * boxes with enough of their own margin can absorb that far enough that the
 * page itself never scrolls while the row's own `.pw-more-cell` (the "More"
 * icon button) still visibly floats outside `.pw-row`'s own border and
 * background — a control detached from the row it belongs to, not a
 * horizontal scrollbar. An independent adversarial review flagged the
 * page-scroll-only metric as insufficient (with its own, differently-wrong
 * arithmetic); re-measuring per-cell containment against the row's own
 * `getBoundingClientRect()` (not just `document`) found the real edge:
 * MEASURED live, in fr (the worst-case language), with `.pw-more-cell`'s
 * right edge against the row's own right edge: the More button paints past
 * its own row from 1100px up to and including 1215px, and stops at 1216px —
 * confirmed identical across 3 repeated runs, unlike the noisy few-pixel
 * jitter the page-scroll metric showed. That 1216px is close to (not
 * identical to) this comment's own 851px/"viewport - 380px" arithmetic
 * predicting 1231px — real fonts and sub-pixel layout are never exactly the
 * back-of-envelope number, which is why this band uses Processes' own
 * 1248.98px rather than the bare measured edge: proven safe already, and
 * gives a real margin (33px) above Apps' own 1216px finding instead of
 * shipping a second value sitting exactly on an edge that a font update
 * could reopen. Both required desktop widths, 1280 and 1440, clear it
 * either way. If this number moves, move it with a measurement of EVERY
 * cell's own position against the row's, not `document.scrollWidth` alone —
 * that is what let the first pass of this fix ship wrong. */
@media (min-width: 1100px) and (max-width: 1248.98px) {
  .pw-app-head { display: none; }

  #invList .pw-app-row {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
  }
  #invList .pw-app-row > .pw-cell {
    width: 100%;
    min-width: 0;
  }
  #invList .pw-app-row > .pw-main-cell { padding-right: 52px; }
  #invList .pw-app-row > .pw-more-cell {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 44px;
  }
  #invList .pw-app-row > .pw-state-cell,
  #invList .pw-app-row > .pw-limit-cell { align-self: stretch; }

  #invList .pw-app-row > .pw-cell:not(.pw-main-cell):not(.pw-state-cell)::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 4px;
    color: var(--haze);
    font-size: 10px;
    font-weight: 750;
    letter-spacing: .06em;
    text-transform: uppercase;
  }
}

@media (max-width: 420px) {
  .pw-toolbar { grid-template-columns: 1fr; }
  .pw-search-tool { grid-column: auto; }
  .pw-state-option,
  .pw-state-control .sw { padding-inline: 5px; font-size: 10px; }
  .pw-icon-label { gap: 4px; }
  .pw-icon { width: 15px; height: 15px; flex-basis: 15px; }
  .pw-pager { grid-template-columns: 1fr 1fr; }
  .pw-page-status { grid-column: 1 / -1; grid-row: 1; }
  .pw-dialog {
    width: calc(100vw - 12px);
    max-height: calc(100dvh - 12px);
    border-radius: 17px;
  }
  .pw-dialog-shell { max-height: calc(100dvh - 12px); }
  .pw-dialog-head { padding: 15px 14px 12px; }
  .pw-dialog-body { padding: 13px 14px 18px; }
  .pw-detail-device { grid-template-columns: 1fr; }
  .pw-detail-device .ghost { width: 100%; }
}

@media (max-width: 340px) {
  /* Three complete words are more valuable than three crushed icon/word
   * pairs on the narrowest supported phone. Icons remain in the limit and
   * details actions; accessible names and visible state words are unchanged. */
  .pw-state-control .pw-icon { display: none; }
  .pw-state-control .pw-icon-label { gap: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .pw-dialog::backdrop { backdrop-filter: none; }
}

/* =========================================================================
 * ONE APP'S SETTINGS — the name that opens it, and what the view holds.
 *
 * THE NAME IS A BUTTON NOW (app.js `inventoryRow`), because the owner asked
 * for exactly that: "make it so you can click on them and it takes you to
 * setting for thea app". These rules make it LOOK like the name it replaced
 * rather than like a control — a pill or a fill on every row of a
 * two-hundred-row list is noise, and the row already carries three real
 * buttons whose shapes mean something. The affordance is carried by the
 * underline on hover and by a real focus ring, not by a box.
 *
 * `button` in app.css sets font-weight 700 and a 13px radius on every button
 * on the page, so both are overridden here rather than inherited: 720 is
 * `.pw-main-name`'s weight, which is what this text used to be.
 * ===================================================================== */
.pw-app-open {
  display: block;
  width: 100%;
  padding: 0;
  border-radius: 4px;
  background: none;
  color: var(--cloud);
  font-size: 14px;
  font-weight: 720;
  line-height: 1.3;
  text-align: left;
  overflow-wrap: anywhere;
}

.pw-app-open:hover { color: var(--glow); text-decoration: underline; }

/* THE TOUCH TARGET IS THE ROW'S, NOT THE TEXT'S, AND THAT IS DELIBERATE. A
 * name is as tall as its own line box; a 44px min-height here would push every
 * row of a two-hundred-row list apart by a blank half-line and turn the screen
 * back into the scrolling document the workspace redesign was built to end.
 * The hit area is widened rather than heightened — the button spans the whole
 * name column (`width: 100%`, `display: block`) and the row's own 14px of
 * padding sits above and below it, which is what a thumb actually lands on.
 * The row's More control remains a full 44px target for the same view. */

.appdetail-now { color: var(--cloud); font-size: 13px; font-weight: 600; }

/* A DIFFERENCE BETWEEN CHILDREN IS A WARNING, NOT A DECORATION. Amber is this
 * page's "something here needs your attention" and mint is "this is fine";
 * painting a per-child disagreement mint would tell a parent the household
 * agrees when it does not. */
.appdetail-differs { color: var(--waiting-text); font-weight: 600; }
.appdetail-same { color: var(--haze); }

/* A REFUSED WRITE IS ATTENTION, THE SAME AMBER AS `.appdetail-differs` —
 * round-4 audit finding #10. This is the sentence #invMsg already carries on
 * the page behind this dialog's own backdrop; painting it any other colour
 * here would make it read as decoration rather than as the reason the switch
 * just snapped back. */
.appdetail-error { color: var(--waiting-text); font-weight: 600; }

.appdetail-child,
.appdetail-device {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
}

.appdetail-child:last-child,
.appdetail-device:last-child { border-bottom: 0; padding-bottom: 0; }

.appdetail-device { grid-template-columns: minmax(0, 1fr); align-items: start; }

.appdetail-child-name,
.appdetail-device-name { display: block; }

.appdetail-child-state { display: block; }

.appdetail-device-unknown { color: var(--haze); font-size: 13px; }

.appdetail-effects {
  display: grid;
  gap: 6px;
  margin: 6px 0 0;
  padding: 0;
  list-style: none;
}

.appdetail-effect {
  color: var(--haze);
  font-size: 12px;
  line-height: 1.45;
  overflow-wrap: anywhere;
  padding-left: 11px;
  border-left: 2px solid var(--line);
}

/* A CONTROL THAT DOES NOT BITE ON THIS DEVICE. Amber, and a heavier rule, for
 * the same reason `.appdetail-differs` is amber: this is the sentence that
 * stops a parent believing a switch is protecting a machine it cannot reach.
 * The words say it too — colour is never the only carrier here. */
.appdetail-inert { color: var(--waiting-text); border-left-color: var(--amber); }

/* The Temporary explanation is what the unavailable Temporary control scrolls
 * to, so it keeps a little room above it inside the dialog's own scroller —
 * without it the heading lands flush against the sticky head and reads as cut
 * off. */
.appdetail-temporary { scroll-margin-block: 10px; }

@media (max-width: 420px) {
  /* The child rows keep their action, stacked, for the same reason
   * `.pw-detail-device` does: a 44px button beside a 60-character child name
   * on a 390px phone crushes the name into a vertical column of letters. */
  .appdetail-child { grid-template-columns: 1fr; }
  .appdetail-child .ghost { width: 100%; }
}
