/*
  Graticula's console stylesheet: one token layer, then components built out of it.

  <b>Redesigned 2026-08-17 to an owner-supplied visual target</b> — a navy sidebar, a bright
  workspace, teal as the working accent and one blue used only for *public*. The brief was explicit
  that this is not a repaint: *"Do NOT make every element use the same font weight, color, border
  treatment, or visual importance."* So the file is ordered tokens → shell → components, and a
  component that needs a new colour takes one from the token block rather than inventing a hex.

  <b>What it replaced, and why the replacement is structural.</b> The old sheet was one flat list
  of selectors that had grown a section at a time; it had four places setting a panel's padding and
  two palettes' worth of greens. Both of today's UI defects (D-46 instances 8 and 10) were shapes
  rebuilt because finding the existing one was harder than writing another. Tokens plus a short
  component list is the cheapest defence against the next one.

  <b>What is deliberately not here.</b> No gradient as a surface, no shadow above 8 px of blur, no
  rounded corner above 8 px, and no badge for anything that is not a state. Those are the four
  things that turn an operations console into a template, and the brief named them.
*/

/* ---------------------------------------------------------------------- tokens */

:root {
  /*
    <b>Two families of surface, because there are two environments.</b> The sidebar is navy and
    everything it holds is light-on-dark; the workspace is near-white and everything in it is
    dark-on-light. Keeping them as separate token sets is what stops a component drifting into the
    wrong one — a "muted text" that works on both would be washed out on one of them.
  */
  /*
    <b>The base is the owner's own number.</b> 2026-08-17: *"soldaki menünün renkleri daha şık. oradaki
    ana renk #171b49, bizdeki #111d34."* They are right about why, and the reason is the hue rather
    than the lightness — the two are within a few points of each other for brightness, but ours sat at
    210° (a blue-grey slate) and theirs at 234° (an indigo). Slate reads as *chrome*; indigo reads as
    *a product decided on a colour*.
    <b>The derived tokens moved with it rather than being kept.</b> A line and a hover surface mixed
    from slate against an indigo base is what makes a palette look almost-right — so `--nav-line`,
    `--nav-on` and the two greys are re-derived at the new hue.
  */
  --nav-base: #171b49;
  --nav-deep: #101336;
  --nav-line: #272c66;
  --nav-ink: #eceefb;
  --nav-muted: #9ea4d2;
  /*
    <b>Lightened 2026-08-19: 3.73:1 on `--nav-base`, which is the dark twin of the `--faint` bug
    above.</b> When `--faint` was fixed for the light surfaces the parallel token on the rail was
    never re-measured. It carries the item count beside a sidebar row — a second fact, but a fact.
    #868dbe is 5.09:1 on #171b49 and stays a step below `--nav-muted` (#9ea4d2, 6.74:1).
  */
  --nav-faint: #868dbe;
  --nav-on: #1e2359;

  /* <b>2026-10-16, the owner's reference screens.</b> White cards on a pale grey page and a white
     header, with blue as the one accent. An hour earlier the grounds were dimmed because the console
     was "aşırı beyaz"; the owner then supplied the target and said *"bu renkler olsun"*, which is
     these. The dark theme (header button) is the answer to glare. */
  --paper: #f4f6f9;
  --surface: #ffffff;
  --surface-2: #f1f4f8;
  --surface-3: #e7ecf3;
  --ink: #101826;
  --muted: #566275;
  /*
    <b>Darkened 2026-08-18 from #8b96a7, which was 2.99:1 on white and below AA.</b> A design review
    caught it on two *new* uses that carry information — a tab's item count and the *by whom* under a
    date — and then it turned out to be twenty uses, nearly all of them text: rail counts, meter
    labels, menu notes, the crossing-surface hint under a layer page. So it was never a decoration
    token; it was a second muted grey that happened to be too light to read.

    <b>#6b7787 is 4.55:1 and the same hue.</b> Nothing structural moves — it stays a step lighter than
    `--muted` (#566275, 5.89:1), which is what it is for: a second fact inside a cell whose first fact
    is already muted. A token that cannot be read is not a lighter shade of a token that can.

    <b>Darkened again 2026-08-19, because 4.55:1 was measured against one ground and used on four.</b>
    Three design reviews the same night measured the same failure independently, and so did an
    arithmetic check afterwards:

    | ground | #6b7787 | #616b7a |
    | --- | --- | --- |
    | `--surface` #ffffff | 4.55 | 5.40 |
    | `--paper` #f6f8fa | **4.28** | 4.71 → 5.07 |
    | `--surface-2` #f1f4f8 | **4.13** | 4.89 |
    | `--surface-3` #e7ecf3 | **3.83** | 4.54 |

    The three failures are not edge cases: `--paper` is the page's own background, and `h4, div.label`
    sits directly on it for every section label on Operations and Sources, as does `.tabstrip .count`
    for the scope counts on Studio's content screen and the group tabs. The 2026-08-18 fix was right
    about the token and checked it in one place.

    <b>#616b7a clears AA on the worst ground it is used on, which is the number that matters.</b> It is
    the lightest value that does — 4.54:1 on `--surface-3` — so the hierarchy above survives: still a
    step lighter than `--muted`, and 5.07:1 against 5.80:1 on the page's own ground.
  */
  --faint: #616b7a;
  --rule: #e3e8ef;
  --rule-strong: #cfd7e3;

  /* <b>The edge of a control, which is not the same thing as a line between two blocks.</b>
     WCAG 2.1 SC 1.4.11 asks 3:1 of the visual information that identifies a component, and a
     text box here has no fill of its own -- its border is the whole of what says *this is a
     box you type in*. Measured 2026-09-05 by computing the ratios rather than judging them:

       edge                      on #ffffff   on #f1f4f8   on #e7ecf3
       --rule        #e3e8ef           1.23         1.12         1.04
       --rule-strong #cfd7e3           1.45         1.31         1.22
       --control-edge #7e889a          3.57         3.24         3.01

     <b>A new token rather than a darker `--rule-strong`.</b> That one is used seventeen times
     and most of them are decoration -- a hover state, a separator dot, a dashed drop zone --
     which the standard does not ask anything of and which a darker grey would make heavier for
     nothing. This one is used only where a boundary is the control.

     <b>#7e889a is the lightest colour on this palette's own hue that clears 3.0 everywhere</b>,
     found by walking the ramp rather than by picking a grey that looked about right: the least
     ink that satisfies the rule, so the form does not turn into a grid of boxes. */
  --control-edge: #7e889a;

  /*
    <b>One working accent and one that means something.</b> Teal is *the product doing something* —
    primary buttons, the active section, focus. Blue appears in exactly one place, the `public`
    sharing state, because that is the one fact on these screens worth a second hue. Violet is the
    third and rarest: it marks the Studio side of the switch, so the two environments are told apart
    by hue and not only by which one is filled in.
  */
  --accent: #1565d8;
  --accent-hot: #1d74ef;
  --accent-soft: #e8f0fe;
  /* Accent text on the soft accent ground: `--accent` is 4.72:1 there; this is 5.78:1 on it and
     6.94:1 on white (re-measured for blue, 2026-10-16). */
  --accent-ink: #1257c2;
  /* Text in the accent hue. --accent is a fill; on navy it is 3.4:1 as text, so text never uses it. */
  --link: var(--accent-ink);
  --accent-line: #bcd2fa;

  --blue: #2f56c8;
  --blue-soft: #eaefff;
  --blue-line: #c3cff5;

  --violet: #6a4bc0;
  --violet-soft: #f0ebfb;
  --violet-line: #d9cdf3;

  /*
    <b>Cyan is the sidebar's accent and teal is the workspace's, and they are not the same colour.</b>
    Owner refinement pass 2026-08-17: *"use a brighter cyan/teal accent for the selected navigation
    item"*, and separately that the page had gone monochrome. A single teal doing both jobs is what
    made it so — on navy it reads as grey-green, so the selected section had no presence. Cyan is
    bright enough to hold its own against a dark surface and would be garish on white, which is
    exactly why there are two.
  */
  --cyan: #4c8fff;
  --cyan-deep: #1d74ef;

  /*
    <b>The primary action's first stop, and it is not `--cyan-deep` — D-233.</b> White on
    `--cyan-deep` is 2.57:1 and the button's label is ordinary text by WCAG's measure, so the
    gradient did not reach 4.5:1 until 53% of its own width. This is the least ink on the same
    hue and saturation that clears it: the ramp walked at 1% steps, where 29% lightness gives
    4.49 and 28% gives 4.79. `--cyan-deep` keeps its own uses, which are decoration and a
    logotype rather than a ground for text.
  */
  --primary-from: #0b7f84;
  --indigo: #2a3f7a;
  --indigo-glow: #3b52a8;

  /*
    Status: hue *and* lightness differ, so a state is never carried by colour alone.

    <b>Two greens, for the reason there are two teals.</b> Owner 2026-08-17: *"service health'taki
    donut'ın renkleri, alttaki lejant renkleri güzel"* — theirs are brighter than ours were, and the
    reason ours were dark is that the same token was doing two jobs: text on a pale badge, where it
    must be dark enough to read, and a dot or an arc, where dark reads as muddy. So `--ok` stays the
    text colour and `--ok-dot` is the indicator, and the same for the other two.
  */
  --ok-dot: #21b56a;
  --warn-dot: #e0a020;
  --alert-dot: #e0503f;

  --ok: #1c7d4d;
  --ok-soft: #e6f4ec;
  --ok-line: #b6ddc6;
  --warn: #8a5a08;
  --warn-soft: #fdf2e0;
  --warn-line: #e8d3a8;
  --alert: #a5342a;
  --alert-soft: #fceceb;
  --alert-line: #edc4c0;

  --mono: ui-monospace, "Cascadia Mono", Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /*
    <b>Five sizes and three weights, and that is the hierarchy.</b> The brief asked for strong
    typographic hierarchy and warned against identical typography for every information level. A
    service name is 15/600; its type and counts are 12.5/400 muted; a section label is 11/600
    uppercase and faint. Nothing else is allowed a size.
  */
  /*
    <b>Raised about a tenth on 2026-08-17, in the tokens rather than per component.</b> Owner polish
    pass: *"increase the overall UI scale slightly (~10%). The reference feels more confident and
    less compressed."* Every size on these screens is one of these five, so the whole interface moves
    together and the ratios between the levels — which are the hierarchy — are unchanged. Editing
    twenty components by hand is how a type scale stops being one.
  */
  --t-page: 26px;
  --t-head: 17px;
  --t-body: 15px;
  --t-small: 13.5px;
  --t-label: 11.5px;

  --gap-1: 4px;
  --gap-2: 8px;
  --gap-3: 12px;
  --gap-4: 16px;
  --gap-5: 22px;
  --gap-6: 32px;

  --round: 5px;
  --round-lg: 8px;

  /*
    <b>Two shadows, both nearly invisible.</b> Depth here comes from the border and the surface,
    not from a glow; the brief's list of things to avoid has "excessive shadows" on it and this is
    what the restraint looks like as a number.
  */
  --lift: 0 1px 2px rgba(16, 24, 38, .05);
  --lift-2: 0 4px 12px -6px rgba(16, 24, 38, .18);

  accent-color: var(--accent);
}

* { box-sizing: border-box; }

/*
  <b>`hidden` has to win, and it did not.</b> The browser's rule for the attribute is
  `[hidden] { display: none }` at user-agent weight, which any author rule with a `display` beats —
  so `#surfaces { display: flex }` kept the Server/Studio switch on screen for a reader allowed only
  one surface, although the router had set `hidden` on it. Found 2026-08-17 the first time a
  publisher account existed to look with; it had been false the whole time. Written as a class
  rather than `#surfaces[hidden]` because the next element given both would fail the same way and
  nobody would connect the two (D-46 instance 9).
*/
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: var(--t-body)/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }

/*
  A visible focus ring, on one rule rather than per control. Keyboard reachability is not a feature
  to remember per element.
*/
/* `--accent`, not `--accent-hot`: the lighter teal was 2.98:1 on the paper, under the 3:1 a focus ring needs. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

/* ----------------------------------------------------------------------- shell */

/*
  <b>One column, and the sections are a bar in the header — ADR-187.</b> The rail that held them is
  gone: ArcGIS Portal and Server Manager both navigate from the top, and a page with folders or
  filters used to have two left columns, the app's and its own. The left column now belongs to the
  page (`.view.withside` below), and a page that needs none has the full width.
*/
#shell { display: block; min-height: 100vh; }

/* The mark, the name and which surface this is, at the head of the bar. */
#brand {
  display: flex; align-items: center; gap: 10px; flex: none;
  color: var(--ink); text-decoration: none;
}
#brand:hover { text-decoration: none; }
#brand .mark {
  width: 28px; height: 28px; flex: none; border-radius: var(--round);
  background: var(--accent); border-radius: 50%; display: grid; place-items: center;
}
#brand .mark svg { width: 16px; height: 16px; display: block; }
#brand .name { font-size: 15px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
/* Teal for Server and violet for Studio, the two halves of the switch beside it. */
#brand .badge {
  font-size: var(--t-label); font-weight: 600; line-height: 1; padding: 4px 8px;
  border-radius: var(--round); background: var(--accent-soft); color: var(--accent-ink);
  border: 1px solid var(--accent-line);
}

/*
  <b>The sections: words with an underline, the way Portal draws its bar.</b> The links stretch to
  the header's full height so the underline sits on the header's own bottom rule, and the selected
  one is teal and heavier — two signals, neither of them only colour.
*/
#tabs { display: flex; align-self: stretch; gap: 2px; margin-left: var(--gap-3); flex: none; }
/* <b>The bar never shrinks; what gives way is named, in order.</b> The first build let `#tabs` shrink, and
   the design review found Studio's last tab painted under the Server|Studio switch at 1,440 pixels and
   focus landing on it there. Now the health line goes first (its dot keeps the sentence as a label),
   then the product's name, and below 1,100 pixels the bar takes a row of its own. */
#brand, #surfaces, header .right, #signout { flex: none; }
#who, #signout { white-space: nowrap; }
#tabs a:focus-visible { outline-offset: -4px; }
/* Studio's five words need the room sooner than Server's four (design review re-measure, 2026-10-16). */
@media (max-width: 1480px) { :root[data-surface="studio"] #healthLine { display: none; } }
@media (max-width: 1260px) { #healthLine { display: none; } }
/* The name used to give way to the section bar below 1,240 pixels; the reader's controls left the bar, so it stays above a phone. */
@media (max-width: 480px) { #brand .name { display: none; } }
#tabs a[data-tab], #tabs a[data-link] {
  display: flex; align-items: center; padding: 0 12px;
  color: var(--muted); font-size: var(--t-body); font-weight: 500;
  text-decoration: none; white-space: nowrap;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
#tabs a[data-tab]:hover, #tabs a[data-link]:hover { color: var(--ink); text-decoration: none; }
#tabs a[data-tab][aria-current="page"] {
  color: var(--link); border-bottom-color: var(--accent); font-weight: 600;
}

/*
  <b>A page's own left column — what Server Manager draws as the grey list beside a page.</b>
  `drawSubpages` puts the siblings of a page there (Site's Data sources, Status and General;
  Security's Members, Roles, Sign-in and Apps) and adds `withside`; every other child of the page
  stays in the second column, in its own order.
*/
.view.withside.on {
  display: grid; grid-template-columns: 200px minmax(0, 1fr);
  column-gap: var(--gap-6); align-items: start;
}
.view.withside > * { grid-column: 2; min-width: 0; }
.view.withside > nav.subpages {
  grid-column: 1; grid-row: 1 / span 60;
  display: flex; flex-direction: column; gap: 2px;
  position: sticky; top: 96px;
  margin: 0; padding: 0; border: 0; background: none;
}
.view.withside > nav.subpages a {
  padding: 8px 12px; border-radius: 0 var(--round) var(--round) 0;
  border: 0; border-left: 3px solid transparent; margin: 0;
  color: var(--muted); font-size: var(--t-body); text-decoration: none;
}
.view.withside > nav.subpages a:hover { background: var(--surface-2); color: var(--ink); }
.view.withside > nav.subpages a[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent-ink);
  border-left-color: var(--accent); font-weight: 600;
}

/*
  <b>An ambient tint under the workspace, and the panels stay white.</b> Owner polish pass: *"the main
  workspace is currently too uniformly gray. Introduce an extremely subtle ambient cyan/violet tint or
  gradient in the page background, especially around the upper content area."*

  <b>Two washes, both under 4%, and neither is a surface.</b> Cyan enters from the left where the
  content starts and violet from the right where the primary action sits, so the tint agrees with
  where the eye already goes. `background-attachment: fixed` keeps it a property of the window rather
  than of the scroll position — a gradient that slides under a long table reads as a rendering fault.
*/
#main {
  min-width: 0; display: flex; flex-direction: column;
  background:
    var(--paper);
  background-attachment: fixed;
}

/* --------------------------------------------------------------------- top bar */

header {
  display: flex;
  align-items: center;
  gap: var(--gap-4);
  padding: 0 clamp(var(--gap-5), 3vw, 44px);
  height: 58px;
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
  position: sticky; top: 0; z-index: 900;
}

header .right { margin-left: auto; display: flex; align-items: center; gap: var(--gap-3); }

/*
  <b>The environment switch, and its two halves are told apart by hue.</b> ADR-034: an
  administrator moves between Server and Studio constantly, and the switch is only in the document
  for somebody who may be in both. Filled is where you are; teal is Server and violet is Studio, so
  the answer to *which one am I in* does not depend on noticing which button is darker.
*/
#surfaces { display: flex; gap: 3px; padding: 3px; background: var(--surface-2);
            border-radius: var(--round); border: 1px solid var(--rule); }
#surfaces a {
  padding: 5px 14px; border-radius: 3px; font-size: var(--t-small); font-weight: 500;
  color: var(--muted); text-decoration: none;
}
#surfaces a:hover { color: var(--ink); text-decoration: none; }
#surfaces a[data-surface="server"][aria-current] { background: var(--accent); color: #fff; }
#surfaces a[data-surface="studio"][aria-current] { background: var(--accent); color: #fff; }

/* `#healthDot` is a `.dot` and takes its states from there. Its own id rules, removed 2026-09-25,
   out-weighed `.dot.ok` and painted every state `--faint`, while refreshHealth() set a class
   (`bad`) that no rule named — so a healthy server showed a grey dot. */
/* It gives way to the section bar: one line, cut with an ellipsis, and the full text in its title. */
#healthLine { font-size: var(--t-small); color: var(--muted); min-width: 0; flex: 0 1 auto;
              white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

#who { font-size: var(--t-small); color: var(--muted); text-align: right; line-height: 1.35; }
#who b { display: block; color: var(--ink); font-size: var(--t-body); font-weight: 600; }

/* --------------------------------------------------------------------- content */

/*
  <b>The workspace uses the width it has.</b> Owner refinement pass: *"the current content area feels
  slightly compressed toward the top-left… use the available desktop width more confidently."* The
  padding grows with the viewport instead of being one number, and the ceiling is high enough that a
  wide screen is filled and low enough that a line of prose never runs to 200 characters.
*/
.wrap { padding: var(--gap-6) clamp(var(--gap-5), 3vw, 44px); max-width: 1720px; }

/*
  <b>A page says what it is.</b> The brief asked for a strong heading and a secondary line, and the
  pair is worth more than it looks: this console has eight screens that were previously
  distinguishable only by which table was on them.
*/
.pagehead { display: flex; align-items: flex-start; gap: var(--gap-4); margin-bottom: var(--gap-6); }

/*
  <b>A stronger title, and the strongest thing on the page.</b> 30 pixels at 650 against a service
  name's 15.5 at 650: the difference is size and nothing else, which is what makes them read as the
  same voice at two levels rather than as two decisions.
*/
.pagehead h2 {
  /* <b>26/600 from 33/650, 2026-09-25, and through `--t-page` at last.</b> Owner: *"a professional
     look and feel"*, chosen as the calmer enterprise direction. The title was the loudest thing on
     every screen and the token that named its size was declared and never read. */
  margin: 0; font-size: var(--t-page); font-weight: 600; letter-spacing: -.02em; line-height: 1.15;
}
.pagehead p { margin: 6px 0 0; color: var(--muted); font-size: 15.5px; }
.pagehead .right {
  margin-left: auto; display: flex; align-items: center; gap: var(--gap-2);

  /* <b>`flex: none`, because the default let a verb break in half.</b> The title block beside it is
     `flex: 0 1 auto` too, so a long subtitle shrank this slot below its buttons' natural width and
     a group's page printed *Add / member* and *Share a / service* on two lines each at 1366px. The
     title is the thing with a good line break in it; a button label is not. */
  flex: none;
}

.lede { color: var(--muted); max-width: 88ch; margin: 0 0 var(--gap-4); }

.panel {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--round-lg);
  box-shadow: var(--lift);
  margin-bottom: var(--gap-4);
}

.panel > table { margin: 0; }

.pad { padding: var(--gap-4) var(--gap-5); }

/*
  A section label: the smallest, faintest thing on the page, and the reason a panel does not need a
  heavier heading to be legible.
*/
/*
  <b>`.label` is a section label and nothing else, and naming it that was a mistake for one build.</b>
  The sidebar's rows carry a `.label` span for their text, so this rule shouted every section name —
  SERVICES, DATA SOURCES, COLLAPSE — in 11-pixel uppercase. A class named after a generic English
  word will collide; the fix is the scope, because renaming one of the two would leave the next
  `.label` to collide again.
*/
h4, div.label, .widget > .label, .rail-head {
  margin: 0 0 var(--gap-3);
  font-size: 14px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--ink);
}
.page h4 { padding-bottom: var(--gap-2); border-bottom: 1px solid var(--rule); }
.page h4 + h4, .page > * + h4 { margin-top: var(--gap-6); }

.hint { font-size: var(--t-small); color: var(--muted); margin: 0 0 var(--gap-3); max-width: 78ch; }
.hint:last-child { margin-bottom: 0; }
.page .hint { max-width: 100%; }

/*
  <b>`text-transform: none` because a value is never a label, and one screen proved it.</b>
  `label.field` uppercases — it is a two-word caption over a control — and `.val` is used *inside* one
  to put a sentence or a fact under the input. So the geodatabase publish screen, which echoes the
  service name into the URL it is about to create, rendered
  `/REST/SERVICES/HOSTED/PROJECT_INFORMATION/FEATURESERVER` in capitals: an operator could not tell
  whether they had typed `Project_Information`, `project_information` or `PROJECT_INFORMATION`, which
  is the one thing that echo exists to show. Design review 2026-08-19. Reset here rather than at the
  call site, because it is wrong everywhere the pattern is used and elsewhere it was only shouting.
*/
.val { font-family: var(--sans); font-variant-numeric: tabular-nums; font-size: var(--t-small); color: var(--muted);
       text-transform: none; letter-spacing: normal; }
.name { font-weight: 600; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); }

code { font-family: var(--mono); font-size: .92em; background: var(--surface-2);
       padding: 1px 5px; border-radius: 3px; color: var(--muted); }

.bad-inline { color: var(--alert); font-size: var(--t-small); }

/*
  A fact worth noticing that is not a failure -- the geodatabase report's flattened
  elevation (D-107). `.bad-inline` would read as *this layer went wrong* and it did not:
  it published, and something about it was lost on the way in.
*/
.warn-inline { color: var(--warn); font-size: var(--t-small); }

.row { display: flex; gap: var(--gap-2); flex-wrap: wrap; align-items: flex-end;
       margin-bottom: var(--gap-3); }

/* ---------------------------------------------------------------------- tables */

table { width: 100%; border-collapse: collapse; font-size: var(--t-body); }

/*
  <b>The header is a label row, not a band.</b> Owner refinement pass: the table *"still feels too
  much like the old interface"*. A filled grey header with a heavy rule under it is the shape of a
  2008 grid; this is uppercase 11-pixel labels on a barely-tinted surface with one hairline, so the
  first strong thing on the screen is a service name rather than the word SERVICE.
*/
th {
  text-align: left;
  padding: 14px var(--gap-4) 12px;
  font-size: 12.5px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--muted);
  background: var(--surface-2);
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}
th:first-child { border-top-left-radius: var(--round-lg); }
th:last-child { border-top-right-radius: var(--round-lg); }

/*
  <b>Taller rows and a lighter separator.</b> The brief asked for row height, vertical alignment and
  subtle row separators in one breath, and they are one thing: at 12 pixels of padding a full-weight
  rule is needed to tell rows apart, and at 18 the eye does it from the spacing, so the rule can drop
  to a tint. Density is unchanged in the sense that matters — the same nine services fit a screen.
*/
/*
  <b>Vertical padding is the row's presence and horizontal padding is the table's budget.</b> The
  10% scale-up pushed the actions column off a 1440-pixel viewport, and the fix is not narrower rows
  — it is 16 pixels of gutter instead of 22, six columns' worth of which is 72 pixels back. Row
  height, which is what the eye reads as generous, is untouched.
*/
td { padding: 15px var(--gap-4); border-bottom: 1px solid #eef1f6; vertical-align: middle; }
tr:last-child td { border-bottom: 0; }

/*
  <b>The preview and the name are one unit, so the gutter between them is small.</b> At the row's own
  padding they read as two columns that happen to be adjacent; at 14 the picture belongs to the name
  beside it, which is what the brief meant by the preview being integrated into the row.
*/
#services td:first-child { padding-right: 14px; }

/*
  <b>The name column takes the width, and its caption does not wrap.</b> `look_EarlyAlert · 3 layers,
  1 group` was breaking across two lines and making one row taller than the rest — a ragged table
  reads as a bug. Giving the column 42% and the caption `nowrap` fixes both, and the states beside it
  need only what their badges are wide.
*/
/*
  <b>The name column takes what is left rather than a fixed share.</b> A 42% width was holding the
  caption on one line and, after the scale-up, shoving the actions off the screen — `auto` plus a
  non-wrapping caption does the first job without the second. The states beside it need only what
  their badges are wide, which is what `width: 1%` on a nowrap cell asks for.
*/
#services .rowmeta { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#services td:nth-child(n+3):not(.svcbody) { width: 1%; white-space: nowrap; }

/*
  <b>The row is clickable and says so once, on hover.</b> Not a cursor change plus a colour plus an
  arrow: one signal. The click itself asks *what was clicked* rather than carrying a list of
  exceptions, which is how a Stop button once opened a service page (D-46).
*/
tr.pick { cursor: pointer; }
tr.pick td { transition: background .1s; }

/*
  <b>Hover tints the row and leaves a teal edge on it.</b> One signal was a background change, which
  on a near-white surface is almost nothing; the edge is what makes *this row is the one under the
  pointer* readable at a glance without darkening the row enough to fight the previews for attention.
*/
tr.pick:hover td { background: var(--surface-2); }
tr.pick:hover td:first-child { box-shadow: inset 3px 0 0 var(--accent-hot); }

/*
  <b>`nowrap` was what made two tables wider than the window.</b> Measured 2026-08-19, after the closed
  menu sheets were fixed and the overflow did not go away: the actions cell held two or three buttons
  and a row menu on one unbreakable line, so Server's services list came to 1100 pixels and Studio's
  content list to 1107 against a 1024 viewport — and the page scrolled sideways, carrying the
  navigation column out with it.

  <b>Wrapping rather than a scroll container, because these tables carry row menus.</b> A
  `widetable` would clip an open dropdown — a scroll box clips absolutely positioned descendants — so
  the fix has to make the row narrower rather than hide it. Two buttons over two lines in a cell nobody
  reads as prose is a fair trade for a page that fits.

  <b>And `display: flex` on the `<td>` was the wrong way to do it</b> — that replaces `table-cell`, so
  the browser wraps the cell in an anonymous one and the content escapes the column instead of wrapping
  inside it. Measured: the buttons still stuck out, now at right=1091 with no ancestor growing to match,
  which is the signature of content leaving its cell. Removing `nowrap` is the whole fix: the buttons
  are inline-blocks with whitespace between them and wrap on their own.
*/
td.acts { text-align: right; }
td.acts > * + * { margin-left: var(--gap-1); }

.empty { color: var(--muted); font-size: var(--t-small); padding: var(--gap-5) var(--gap-4); }

/* --------------------------------------------------------------------- buttons */

button, .btn {
  font: 500 var(--t-small) var(--sans);
  padding: 7px 14px;
  border: 1px solid var(--control-edge);
  border-radius: var(--round);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
button:hover { background: var(--surface-2); }
button:disabled { opacity: .45; cursor: default; }
button:disabled:hover { background: var(--surface); }

/*
  <b>Superseded 2026-09-25 by the rule below; kept as the record of D-233.</b> Until then the
  primary action carried the product's gradient, the only one in the workspace. Owner refinement pass: *"visually give it the stronger cyan → blue/violet treatment
  from the approved design."* One element having it is what makes it read as *the* action; two would
  make it a theme, which is on the list of things to avoid.

  <b>Its first stop is `--primary-from` and not `--cyan-deep` — [D-233](../../../docs/architecture-debt.md).</b>
  White on `--cyan-deep` is **2.57:1**, and the label is 15px at weight 600, which is ordinary text
  by WCAG's measure and not large. The gradient only reached 4.5:1 at **53% of its own width**, so
  *Sign in*, *Add a connection…*, *Go* and *Resume* all failed 1.4.3 across their leading half on
  every screen. `--primary-from` is the least ink on this same hue and saturation that clears it —
  the ramp walked at 1% steps, 29% lightness gives 4.49 and 28% gives 4.79 — which is how
  `--control-edge` was chosen for [D-215](../../../docs/architecture-debt.md) and for the same
  reason: a colour picked by eye is a colour that drifts back.

  <b>A darker stop rather than a text shadow</b>, which is what the web usually reaches for and is
  the worst of the three options the row named: it trades a ratio anybody can compute for one
  nobody can.
*/
/* <b>Solid teal, 2026-09-25.</b> The teal-blue-violet gradient and its coloured glow were the one
   decorative surface on a working screen; the calmer enterprise direction makes the primary the
   accent and nothing else. White on `--accent` is 5.0:1, and 4.8:1 under the hover filter. It
   keeps its weight and loses its size: the same height as the buttons beside it. */
button.primary {
  background: var(--accent);
  border-color: var(--accent); color: #fff; font-weight: 600;
  padding: 7px 16px;
  box-shadow: var(--lift);
  transition: filter .12s;
}
button.primary:hover {
  background: var(--accent);

  /*
    <b>Hover darkens rather than brightens — D-233's second half, which nothing had named.</b>
    `brightness(1.06)` lightened the ground and left the text at white, because white is already
    clipped: the middle stop went 4.88 → 4.41 and the whole button failed AA *on hover* even
    before its first stop was corrected. So the state that says *you are about to press this* was
    the least readable one. Darkening filters the label too — white becomes #f0f0f0 — and the
    worst stop still measures 4.67:1. A filled button that deepens under the pointer is also the
    ordinary affordance; brightening one was never more than a habit.
  */
  filter: brightness(.9);
}
button.primary .plus { font-weight: 400; margin-right: 7px; opacity: .9; }

button.tiny { padding: 5px 10px; font-size: 12px; }
button.ghost { border-color: transparent; background: none; color: var(--muted); }
button.ghost:hover { background: var(--surface-2); color: var(--ink); }

/*
  <b>Destructive actions do not compete.</b> The brief: *"Delete should NOT compete visually with
  normal actions."* So `danger` is not a red button — it is an ordinary one whose text turns red on
  hover, and it lives in the overflow menu where the row is concerned.
*/
button.danger { color: var(--alert); }
button.danger:hover { background: var(--alert-soft); border-color: var(--alert-line); }

/*
  The overflow menu: `<details>` rather than a script-driven popover, because the element already
  keeps the open/close state. It does not close on Escape or on a press outside; console.js does both
  for every menu (sweep 2026-10-19).
*/
.menu { position: relative; display: inline-block; }
.menu > summary {
  list-style: none; cursor: pointer;
  padding: 5px 9px; border: 1px solid transparent; border-radius: var(--round);
  color: var(--muted); font-size: 15px; line-height: 1;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary:hover { background: var(--surface-2); color: var(--ink); }
.menu[open] > summary { background: var(--surface-2); border-color: var(--rule); }
/*
  <b>A closed menu takes no space, and it was taking 450 pixels.</b> Measured 2026-08-19 while chasing
  a horizontal overflow the design reviews found on four screens: a closed `<details>` still gave its
  `.sheet` a layout box — `display: block`, `offsetParent` non-null, 450px wide, its right edge at
  x=1083 in a 1024 viewport — so ten row menus were what pushed `document.body.scrollWidth` to 1100 on
  Server's services list and 1107 on Studio's content list. The tables themselves fit: 820 pixels
  inside an 820-pixel panel.

  <b>Which is why neither of those tables is wrapped in `widetable`.</b> Wrapping them would have
  hidden the symptom and clipped every open menu, since a scroll container clips absolutely positioned
  descendants. Members and the group's Content tab *are* wrapped, because those tables are genuinely
  wider than their panels and carry no row menu.
*/
.menu:not([open]) .sheet { display: none; }

.menu .sheet {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 800;
  min-width: 210px; padding: var(--gap-1);
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--round);
  box-shadow: var(--lift-2);
  text-align: left;
}
.menu .sheet button {
  display: block; width: 100%; text-align: left; border: 0; background: none;
  padding: 8px 10px; border-radius: 3px; font-weight: 400;
}
.menu .sheet button:hover { background: var(--surface-2); }
/* A link in a menu is one of its items, not a link in a sentence: the buttons' block, padding and ink. It was blue,
   larger and flush left above 760 pixels (UX sweep 2026-10-19). */
.menu .sheet a {
  display: block; padding: 8px 10px; border-radius: 3px; color: var(--ink);
  font: 400 var(--t-small) var(--sans); text-decoration: none;
}
.menu .sheet a:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.menu .sheet a.danger { color: var(--alert); }
.menu .sheet a.danger:hover { background: var(--alert-soft); color: var(--alert); }
.menu .sheet .note { padding: 6px 10px 8px; color: var(--faint); font-size: 11.5px; }

/* -------------------------------------------------------------------- controls */

input, select, textarea {
  font: var(--t-small) var(--sans);
  padding: 7px 10px;
  border: 1px solid var(--control-edge);
  border-radius: var(--round);
  background: var(--surface);
  color: var(--ink);
}
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
textarea { font-family: var(--mono); width: 100%; line-height: 1.45; }
select { padding-right: 6px; }

label.field {
  display: flex; flex-direction: column; gap: 3px;
  font-size: 13px; letter-spacing: 0; text-transform: none;
  color: var(--muted); font-weight: 600;
}

/* A number reads as a sentence, with the unit outside the box. */
/* Sign-in providers — ADR-088: a provider's way in on the sign-in panel, and a checkbox label in a form. */
#signinProviders { margin: 4px 0 14px; }
/* A secondary button, drawn as one: the form's own Sign in is the page's one primary action. */
#signinProviders a.provider {
  display: block; text-align: center; margin-bottom: 8px; text-decoration: none;
  padding: 10px 16px; border: 1px solid var(--control-edge); border-radius: var(--round);
  background: var(--surface); color: var(--ink); font-weight: 600;
}
#signinProviders a.provider:hover { background: var(--surface-2); }
#idpForm .check { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
#idpForm .row input[readonly] { flex: 1; font-family: var(--mono, monospace); }
#idpForm input[type=text], #idpForm input[type=url], #idpForm input[type=password] { width: 100%; max-width: 520px; }
#idpForm h5 { margin: 18px 0 4px; }
#idpForm .hint { margin: 2px 0 8px; }
.idpissuer code { overflow-wrap: anywhere; }
.idpissuer .hint { display: block; margin: 0; }
#idpGroups .valuesgrid input[type=text] { width: 100%; min-width: 14ch; }
#idpGroups .valuesgrid select { min-width: 16ch; }
#idpGroups .tablewrap { overflow-x: auto; max-width: 100%; }
#idpGroups .picker { min-width: 0; max-width: 100%; }
#idpGroups .row.left, #idpForm .row.left { justify-content: flex-start; }
#idpForm #idpIssuer, #idpForm #idpClient, #idpForm #idpUserBase, #idpForm #idpUserFilter { font-family: var(--mono, monospace); }
#view-signin .tablewrap { overflow-x: auto; max-width: 100%; }
/* <b>Any control the page marks invalid, since 2026-09-25.</b> This was scoped to `#idpForm`, so the
   three other places console.js sets `aria-invalid` (a field map, a schedule's minutes, a refused
   value) announced the error and drew nothing. */
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] {
  border-color: var(--alert);
}
@media (max-width: 480px) {
  /* The reply URL and redirect URI were cut to their host at 390 px: the box takes the line, Copy the next. */
  .idpform .row { flex-wrap: wrap; }
  .idpform .row input[readonly] { flex: 1 1 100%; }
}
/* The .sr-only header of the provider list escaped the card at 390 px: its absolute box needs a positioned card. */
#view-signin .panel { position: relative; }
/* An empty list is a sentence, not column headings over one (sweep 2026-10-19). */
#view-signin table:has(td.empty) thead { display: none; }
#view-signin td.empty { padding: var(--gap-5); }
#appRows button.danger { border-color: var(--alert-line); }
@media (max-width: 719px) {
  #view-signin .panel table.rowblocks { min-width: 0; }
  #idpRows td.idpacts { grid-column: 1 / -1; grid-row: auto; text-align: left; }
  #view-signin table.rowblocks code { overflow-wrap: anywhere; }
}

/* At a phone's width the list scrolls inside its card, as the Domains screen's does. */
@media (max-width: 760px) {
  #view-signin .panel { overflow-x: auto; }
  #view-signin .panel table { min-width: 560px; }
  #view-signin .panel table:has(td.empty) { min-width: 0; }
  #view-signin .panel table:has(td.empty) thead { display: none; }
}

/* Where a shared domain is used — ADR-087. A short list under its count, one field a line. */
.domainuses { margin: 4px 0 0; padding-left: 1.1em; font-size: var(--t-small); }
.domainuses li { padding: 2px 0; }
#valuesShared { max-width: 100%; }

/* At a phone's width the table scrolls inside its card rather than the page sideways — design review
   2026-09-24 measured 539 px of page in a 390 px window — and an empty one does not size itself by its head. */
@media (max-width: 760px) {
  .domainstable { overflow-x: auto; }
  .domainstable table { min-width: 460px; }
  .domainstable table:has(td.empty) { min-width: 0; }
  .domainstable table:has(td.empty) thead { display: none; }
  .valuesedit { overflow-x: auto; }
}

/* The map ground — ADR-086. The available services never move; the stack is the drawing order, top first. */
.groundpick { border: 0; padding: 0; margin: var(--gap-3) 0 0; max-width: 620px; }
.groundpick legend { padding: 0; margin-bottom: var(--gap-2); font-size: var(--t-body); }
.groundopt {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; column-gap: var(--gap-2);
  align-items: center; min-height: 44px;
}
.groundopt input { width: 18px; height: 18px; margin: 0; }
.groundopt label { overflow-wrap: anywhere; }
.groundopt .warn-inline { grid-column: 2; }
.pill.groundpill { background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn); }
.pill.groundpill::before { display: none; }
.groundcount { font-weight: 400; color: var(--muted); margin-left: var(--gap-2); font-size: var(--t-small); }
.groundstack { margin: var(--gap-2) 0 0; padding: 0; list-style: none; max-width: 620px; }
.groundstack li {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px var(--gap-2);
  min-height: 44px; border-bottom: 1px solid var(--rule);
}
.groundstack .groundname { font-weight: 600; }
.groundstack .warn-inline { flex-basis: 100%; order: 3; padding-bottom: 6px; }
.groundmove { margin-left: auto; display: inline-flex; gap: 4px; }
.groundmove button { min-width: 44px; min-height: 44px; }
@media (max-width: 600px) { #view-settings .row .tiny { min-height: 44px; } }

.setting { display: flex; align-items: center; gap: var(--gap-3); padding: 7px 0; max-width: 620px; }
.setting .q { flex: 1; font-size: var(--t-body); }
/* 116px suits a number with a unit beside it, which is what every other setting on this page is. A
   checkbox is not one: measured 2026-08-19, the delete lock came out 116 pixels wide with its own
   Locked label pushed to the far side of the box, so the row read as two unrelated things. */
.setting input:not([type=checkbox]), .setting select {
  width: 116px; text-align: right; font-family: var(--mono); }
.setting .u { width: 68px; color: var(--muted); font-size: var(--t-small); }
/* Under a limit's box: whether it is set here or inherited, and what is in force (UX sweep 2026-10-19, finding 6). */
.hint.inherit { margin: 0 0 var(--gap-3); max-width: 620px; }
.inheritstate { display: block; color: var(--ink); }
.inheritstate .tag {
  display: inline-block; margin-right: 8px; padding: 0 6px; border: 1px solid var(--rule-strong); border-radius: 4px;
  color: var(--muted); font-size: 11.5px; font-weight: 600; line-height: 18px; vertical-align: 1px;
}
.inheritstate .tag.set { color: var(--accent-ink); border-color: var(--accent-line); background: var(--accent-soft); }
.inheritstate:empty { display: none; }
/* Links in running text are underlined, not told apart by colour alone (UX sweep 2026-10-19, finding 15). */
:root[data-surface="server"] #view-service p a:not(.btn),
:root[data-surface="server"] #view-service dd a:not(.btn), #view-operations p a:not(.btn), #view-sources p a:not(.btn),
#view-settings p a:not(.btn), #view-logs p a:not(.btn) { text-decoration: underline; text-underline-offset: 2px; }

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 2px var(--gap-5); max-width: 580px; }
.grid2 label { display: flex; align-items: center; gap: var(--gap-2); padding: 5px 0;
               font-size: var(--t-body); }
.grid2 label.off { color: var(--faint); }
/* One option a line, with its note under it rather than squeezed into a grid cell (UX sweep 2026-10-19, finding 20). */
.checklist { display: grid; gap: 2px; max-width: 580px; }
.checklist label { display: flex; align-items: center; gap: var(--gap-2); padding: 5px 0; font-size: var(--t-body); }
.checklist .hint { margin: -4px 0 4px 26px; }

.toolbar {
  display: flex; align-items: center; gap: var(--gap-3);
  padding: var(--gap-3) var(--gap-4);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--round-lg);
  margin-bottom: var(--gap-4);
  box-shadow: var(--lift);
}
.toolbar input[type=search], .toolbar input[type=text] { flex: 0 1 360px; }
/*
  <b>Wrapping, because one of these is a sentence.</b> `nowrap` was right for the short values it was
  written for and made the page scroll sideways at 1024px on the Anonymous view, whose toolbar carries
  *"Three requests per layer: the service document, the layer document, and a count query."* Measured
  2026-08-19: `body.scrollWidth` 1054 against a 1024 viewport, and no other toolbar overflowed — so the
  rule was one long value away from being wrong rather than wrong in general. A toolbar is a row of
  facts, and a fact that needs two lines takes two.
*/
.toolbar .val { min-width: 0; }

/* Ten rows a page on Server's listings — owner 2026-08-18. Sits under the panel it pages,
   right-aligned, because that is where a reader's eye finishes a table. Nothing but the two
   arrows and which rows are on screen: a pager that offers page numbers on a list of a
   thousand offers a hundred links nobody clicks. */
.pager {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--gap-3);
  margin: calc(-1 * var(--gap-2)) 0 var(--gap-4);
  padding: 0 var(--gap-2);
}
.pager .val { white-space: nowrap; }
.pager button { min-width: 2.2em; }

dl.facts { display: grid; grid-template-columns: auto 1fr; gap: 6px var(--gap-4); margin: 0;
           font-size: var(--t-small); }
dl.facts dt { color: var(--muted); }
/*
  <b>`overflow-wrap: anywhere`, not `word-break: break-all`.</b> `break-all` breaks every word
  at the edge whether it needs to or not, so a facts list 900 pixels wide split `JPEG` into
  `JPE` and `G`. `anywhere` breaks only a word that cannot fit — which is what these values
  need, because a path or a format list has no spaces to break at and a wkid has nothing to
  gain from being split at all.
*/
dl.facts dd { margin: 0; font-family: var(--mono); overflow-wrap: anywhere; }
dl.facts dd .mono { font-weight: 600; color: var(--ink); }
dl.facts dd .why { font-family: var(--sans); font-weight: 400; word-break: normal;
                   color: var(--muted); }

.group { border-top: 1px solid var(--rule); padding-top: var(--gap-4); margin-top: var(--gap-4); }
.group:first-child { border-top: 0; padding-top: 0; margin-top: 0; }

/* ---------------------------------------------------------------------- states */

/*
  <b>A badge is for a state, and for nothing else.</b> The brief: *"Do not turn every piece of
  metadata into a badge."* Two things on these screens are states — whether a service answers, and
  who may read it — so two families of badge exist and a count or a name never gets one. The dot
  carries the same information as the colour, so a state is not colour alone.
*/
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 500;
  padding: 3px 9px 3px 8px;
  border-radius: 4px;
  border: 1px solid var(--rule);
  background: var(--surface-2);
  color: var(--muted);
  white-space: nowrap;
}
.pill::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor;
                flex: none; opacity: .8; }

/*
  <b>A sharing scope gets a glyph instead of a dot.</b> Owner 2026-08-17: *"sharing'deki icon mantığı
  güzel."* It is — *public* and *private* are the two facts on this screen somebody scans for, and a
  globe and a padlock are read before a word is. The dot is suppressed where an icon replaces it, so
  a badge never carries both; and the icon inherits `currentColor`, so the three scopes keep the
  hue that already told them apart.
*/
.pill.withicon::before { display: none; }
.pill svg { width: 12px; height: 12px; flex: none; }
.pill svg * { vector-effect: non-scaling-stroke; }

/* The same treatment on a button: a verb reads faster with its shape beside it. */
button .ico { font-size: 9px; margin-right: 7px; opacity: .75; vertical-align: 1px; }
button:hover .ico { opacity: 1; }

.pill.p-started, .pill.p-ok, .pill.p-usable, .pill.p-active {
  background: var(--ok-soft); border-color: var(--ok-line); color: var(--ok);
}
.pill.p-stopped, .pill.p-degraded, .pill.p-disabled {
  background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn);
}
.pill.p-unusable, .pill.p-delete { background: var(--alert-soft); border-color: var(--alert-line);
                                   color: var(--alert); }
/* Started and failing: requests reach it and get a server error (UX sweep 2026-10-19, finding 1). */
.pill.p-failing { background: var(--alert-soft); border-color: var(--alert-line); color: var(--alert); }

/* <b>Running and refusing, which is a state and not a status — D-232.</b> It sits *beside* the
   started pill rather than replacing it, so it is deliberately the quieter of the two: amber
   like `stopped`, because the consequence is the same to a client, and not red, because nothing
   has failed and the end is already scheduled. `--warn` carries the tone; the row that draws it
   says why in its title. */
.pill.p-refusing { background: var(--warn-soft); border-color: var(--warn-line);
                   color: var(--warn); margin-left: 6px; }

.pill.p-public { background: var(--blue-soft); border-color: var(--blue-line); color: var(--blue); }
.pill.p-organization { background: var(--violet-soft); border-color: var(--violet-line); color: var(--violet); }
.pill.p-private { background: var(--surface-3); border-color: var(--rule-strong);
                  color: var(--muted); }

/* Not a state: hosted/registered is a fact about where the data lives, so no dot and no colour. */
.pill.p-hosted, .pill.p-registered { background: var(--surface-2); color: var(--muted); }
.pill.p-hosted::before, .pill.p-registered::before { display: none; }

/* <b>Kept warm (ADR-218) is a state, and a calm one</b>: nothing is wrong with a pinned service, so it wears the neutral
   ground and no colour — the head card's started/stopped pill stays the one that is read first. */
.pill.p-pinned { background: var(--surface-2); color: var(--muted); }
#heroPin:empty { display: none; }
#servicePinSays:empty { margin: 0; }

/* The Parameters page's Kept warm section: the state, then its one button beside it rather than across the page. */
.pinrow { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2) var(--gap-4);
          margin: var(--gap-3) 0; }
.pinstate { margin: 0; flex: 0 1 auto; color: var(--ink); }
.pinlist { margin: 0 0 var(--gap-3); padding-left: var(--gap-5); font-size: var(--t-small); columns: 2 220px; }
.pinlist li { margin-bottom: var(--gap-1); break-inside: avoid; }
/* The page's own lead-in about the boxes starts a new group after this section, not a fourth line of it. */
#servicePinBox:not([hidden]) + .hint { margin-top: var(--gap-6); }

.swatch { display: inline-block; width: 9px; height: 9px; border-radius: 2px;
          margin-right: 7px; vertical-align: 0; border: 1px solid rgba(0, 0, 0, .3); }

/* ------------------------------------------------------- Server's own furniture */

/*
  <b>Folders, operational widgets, and the service list, in two columns.</b> The left column is
  *where am I and how is it* and the right is *what is here*. The brief asked for clear separation
  between navigation, operational information and service data, and this is that separation as a
  grid rather than as more borders.
*/
#serverGrid { display: grid; grid-template-columns: 282px minmax(0, 1fr); gap: var(--gap-5);
              align-items: start; }
/*
  <b>`minmax(0, 1fr)` and not `1fr`, which is the whole of why this page scrolled sideways.</b> A bare
  `1fr` is `minmax(auto, 1fr)`, and `auto` as a minimum means **min-content** — so the column could
  never be narrower than the widest thing in it, whatever that thing's own `overflow` said. The desktop
  rule above already had it right; the narrow one, which is the case that needs it, did not.

  Measured 2026-08-19 by hiding subtrees until `document.body.scrollWidth` fell: services list 1100
  against a 1024 window, and the last thing standing was the table's own header row setting a
  min-content the grid then honoured. Wrapping the table in `widetable` did nothing until this changed,
  because the panel was being sized to its content rather than sizing its content.
*/
@media (max-width: 1080px) { #serverGrid { grid-template-columns: minmax(0, 1fr); } }

#folderRail { background: var(--surface); border: 1px solid var(--rule);
              border-radius: var(--round-lg); overflow: hidden; box-shadow: var(--lift);
              margin-bottom: var(--gap-4); }

.rail-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--gap-3) var(--gap-3) var(--gap-3) var(--gap-4);
  border-bottom: 1px solid var(--rule);
  margin: 0;
}

.rail-item {
  display: flex; align-items: center; gap: 11px;
  padding: 11px var(--gap-4);
  border-left: 3px solid transparent;
  color: var(--ink); font-size: var(--t-body); text-decoration: none;
}
.rail-item:hover { background: var(--surface-2); text-decoration: none; }
.rail-item { transition: background .1s; }
.rail-item.on {
  background: var(--accent-soft);
  border-left-color: var(--accent);
  font-weight: 600;
}
/*
  <b>The rail's icons, and the root is not a folder.</b> Owner 2026-08-17: *"folders ekranının
  simgeleri güzel."* The reference draws a house for the site root and a folder for the rest, which
  is a real distinction rather than decoration: the root is *where a service lives when nobody put
  it anywhere*, and giving it the same icon as `turkiye` would say it is one folder among four.
*/
.rail-item svg { width: 15px; height: 15px; flex: none; color: var(--faint); }
.rail-item.on svg { color: var(--link); }
.rail-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-count { margin-left: auto; font: var(--t-small)/1 var(--sans); font-variant-numeric: tabular-nums; color: var(--muted); }
.rail-item.on .rail-count { color: var(--accent-ink); }

/*
  <b>An operational widget, and it reports what the server actually measures.</b> The brief asked
  for Service health and Server resources. Health is real — the listing carries every service's
  status. Resources are real too, but not as the reference's CPU/Memory/Disk percentages: this
  server reports process CPU time, managed heap bytes and tile-cache size, and there is no disk
  quota to be a percentage of. So the widget shows the figures it has with their units, and there
  are no sparklines, because a sparkline claims a history nothing here keeps.
*/
.widget { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--round-lg);
          box-shadow: var(--lift); padding: var(--gap-4); margin-bottom: var(--gap-4); }
.widget > .label { margin-bottom: var(--gap-3); display: flex; align-items: center;
                   gap: var(--gap-2); }

/*
  <b>Site › Status leads with a verdict</b> (UX sweep 2026-10-19, finding 10): one sentence, the counts behind it, and
  the services that need attention. It replaced a donut and its legend, which could not show a failing service.
*/
.verdict { margin-bottom: var(--gap-5); }
.verdict h3 { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px 10px; margin: 0 0 var(--gap-4);
  font-size: 22px; font-weight: 650; color: var(--ink); text-transform: none; letter-spacing: 0; }
.verdict h3 .big { font-size: 34px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.verdict.bad h3 .big { color: var(--alert); }
.verdictcounts { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: var(--gap-3); margin: 0; }
.verdictcounts > div { border: 1px solid var(--rule); border-radius: var(--round-lg); padding: var(--gap-3);
  background: var(--surface-2); min-width: 0; }
.verdictcounts dt { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: var(--t-small); }
.verdictcounts dt svg { width: 13px; height: 13px; flex: none; }
.verdictcounts dd { margin: 4px 0 0; font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); }
.verdictcounts .bad { background: var(--alert-soft); border-color: var(--alert-line); }
.verdictcounts .bad dt, .verdictcounts .bad dd { color: var(--alert); }
.verdictcounts .warn { background: var(--warn-soft); border-color: var(--warn-line); }
.verdictcounts .warn dt { color: var(--warn); }
.verdictissues { list-style: none; margin: var(--gap-4) 0 0; padding: 0; border-top: 1px solid var(--rule); }
.verdictissues[hidden] { display: none; }
.verdictissues li { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2) var(--gap-4);
  padding: var(--gap-3) 0; border-bottom: 1px solid var(--rule); }
.verdictissues li p { flex: 1 1 220px; min-width: 0; margin: 0; overflow-wrap: anywhere; }
.verdictissues a:not(.btn) { text-decoration: underline; }
.widget #rwWhy { margin: var(--gap-3) 0 0; }
/* Every route, behind a disclosure: a summary line that opens a table that scrolls inside itself. */
.routelist { margin: 0 0 var(--gap-5); }
.routelist > summary { cursor: pointer; padding: var(--gap-2) 0; font-weight: 600; color: var(--ink); }
.routelist > summary .val { margin-left: 4px; font-weight: 400; }
.routelist .routetable { max-height: 70vh; overflow: auto; margin-top: var(--gap-3); }
.routelist .routetable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/*
  <b>A resource row: name, sparkline, value.</b> The brief asked for the reference's sparklines and,
  in the same breath, for the real metrics to be preserved rather than replaced by invented
  percentages. Both are possible: the line is drawn from samples this page has actually taken since
  it opened, so it is a real history of a real measurement rather than a shape.
*/
/*
  <b>The value is the thing, and it now looks like it.</b> Owner polish pass: *"make the values and
  charts more visually prominent."* The number goes to 15 in the monospace face at 600, the chart to
  28 pixels tall, and the metric's name drops to the label size — so the row reads value first, shape
  second, name last, which is the order somebody scanning an operations widget wants.
*/
.meter { display: grid; grid-template-columns: 52px 1fr 82px; align-items: center;
         gap: var(--gap-3); padding: 10px 0; font-size: var(--t-small);
         border-top: 1px solid var(--rule); }
.meter:first-child { border-top: 0; }
.meter .k { color: var(--faint); font-size: var(--t-label); font-weight: 600;
            letter-spacing: .04em; text-transform: uppercase; }
.meter .t { font: 600 15px/1 var(--sans); font-variant-numeric: tabular-nums; color: var(--ink); text-align: right;
            font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
.meter svg { display: block; width: 100%; height: 28px; overflow: visible; }
.meter svg path.line { fill: none; stroke: var(--violet); stroke-width: 1.75;
                       stroke-linejoin: round; stroke-linecap: round; }
.meter svg path.area { fill: rgba(106, 75, 192, .12); stroke: none; }
.meter.warm svg path.line { stroke: var(--cyan-deep); }
.meter.warm svg path.area { fill: rgba(15, 179, 186, .12); }
.meter .waiting { font-size: 11px; color: var(--faint); }

/*
  The picture box. It holds a map this server rendered — `/admin/thumbnail`, D-58 — drawn from
  every feature of the cover layer and its own symbology, cached, and shown at two sizes from one
  render. It was a canvas the browser filled from a sample of up to 800 features until 2026-09-02,
  which for a dense layer drew a fraction of it and read as *nearly empty*. The border is the
  lightest one available, because the picture is the thing and its frame is not.
*/
/*
  <b>The preview sits on the row rather than in a box on it.</b> Owner refinement pass: it *"should
  feel integrated into the row rather than placed inside a generic gray rectangle."* So the grey
  fill is gone — the canvas is white like the row, held by a hairline and a shadow small enough to
  read as a lift rather than a frame. The geometry keeps its own colours, which is the point: cyan,
  yellow and pink coming out of the data are most of this screen's personality (ADR-033 §5b).
*/
/*
  <b>Measured against the reference rather than chosen.</b> The polish pass asked for a 10% scale-up
  *and* for the populated table to be compared with the approved screenshot — and those pull opposite
  ways here, because the reference's own rows are tighter than what a proportional increase produces.
  Its preview is about 82×50 in a 77-pixel row; a straight +10% on my previous 112×78 gave 122×84 in a
  121-pixel row, which is half again as tall as the thing it was being compared to.

  <b>So the type keeps the increase and the picture does not.</b> 104×70 in a ~101-pixel row: the
  names, labels and folder panel are a tenth larger as asked, and the row is much closer to the
  reference's density. The two instructions cannot both be satisfied exactly, and this is the split —
  said out loud because the alternative is silently picking one.
*/
/*
  <b>`object-fit: cover` because the picture is one size and the slots are two — D-58.</b> The
  server renders 336x224 so a high-density screen has pixels to use and one cached picture serves
  both the 104x70 list slot and the 168x112 one on a service's own page. Without this an `<img>`
  would be stretched to whichever box it landed in; the ratios are close enough that `cover`
  crops nothing anybody can see and guarantees no distortion when a future slot is shaped
  differently.
*/
.thumb { display: block; width: 104px; height: 70px; border-radius: 7px;
         object-fit: cover;
         background: var(--surface);
         box-shadow: 0 0 0 1px var(--rule), 0 1px 2px rgba(16, 24, 38, .05); }
tr.pick:hover .thumb { box-shadow: 0 0 0 1px var(--rule-strong), 0 2px 6px -2px rgba(16, 24, 38, .12); }

/*
  <b>While the picture is being fetched, the slot says so — D-58, from the design review.</b> A
  thumbnail that has not arrived is a flat box, and a flat box is exactly what a genuinely sparse
  layer's picture looks like: on a cold cache an operator reads a table of them as *there is
  nothing in any of these*. The sweep clears `data-thumb` as it fills each one, so this selector
  is true only while the picture is outstanding and needs no second attribute to be maintained.

  <b>A slow shimmer rather than a spinner.</b> Forty spinners in a table is a fairground; what is
  needed is only that *waiting* and *empty* stop looking the same. `prefers-reduced-motion` gets
  the flat tint without the movement, which still separates the two.
*/
img.thumb[data-thumb]:not([src]) {
  background: linear-gradient(100deg,
              var(--surface) 30%, var(--rule) 50%, var(--surface) 70%) var(--surface);
  background-size: 300% 100%;
  animation: thumb-waiting 1.4s ease-in-out infinite;
}

@keyframes thumb-waiting {
  from { background-position: 150% 0; }
  to   { background-position: -50% 0; }
}

@media (prefers-reduced-motion: reduce) {
  img.thumb[data-thumb]:not([src]) { animation: none; background: var(--rule); }
}

.thumb.empty { position: relative; background: repeating-linear-gradient(
                 135deg, #f7f9fb 0 6px, #f1f4f8 6px 12px); }
.thumb.empty::after { content: "—"; position: absolute; inset: 0; display: grid;
                      place-items: center; color: var(--faint); font-size: 12px; }

/*
  <b>The reason in the box, when it is short enough to be read there — D-58.</b> The canvas this
  replaced wrote *stopped* where the picture would be; the hatch's em dash means *nothing to
  draw*, which is a different fact and must keep its own mark. D-58's own text calls hover text
  "a weak place to carry a correction", so a correction that fits goes in the box.
*/
.thumb.empty[data-note]::after { content: attr(data-note); font-size: 10.5px;
                                 letter-spacing: .02em; }

/*
  <b>The name is the row, and its metadata is a caption.</b> Four differences at once, which is what
  the brief meant by *not just font sizes*: 15.5 against 12.5, 650 against 400, ink against muted,
  and the counts in the monospace face because they are quantities. The separator is a hairline dot
  at rule strength — visible enough to group, too faint to read as punctuation.
*/
td .name { display: block; font-size: 17px; font-weight: 650; letter-spacing: -.012em;
           color: var(--ink); line-height: 1.25; }
.rowmeta { display: block; font-size: var(--t-small); color: var(--muted); font-weight: 400;
           margin-top: 3px; }
.rowmeta .sep { color: var(--faint); margin: 0 7px; }
.rowmeta .count { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------- the map */

#mapPanel { display: none; margin-bottom: var(--gap-4); }
#mapPanel.on { display: block; }
#view { height: 460px; background: var(--surface-2); border-radius: var(--round-lg);
        overflow: hidden; }
#view:empty::after { content: "the map loads when a layer is shown"; display: grid;
                     place-items: center; height: 100%; color: var(--faint);
                     font-size: var(--t-small); }
#legend { display: flex; flex-wrap: wrap; gap: var(--gap-3); padding: var(--gap-3) 0 0;
          font-size: var(--t-small); color: var(--muted); }
#basemapBar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2) var(--gap-3);
  padding: var(--gap-3) var(--gap-4); border-bottom: 1px solid var(--rule);
}
#basemapBar .lbl { font-size: var(--t-label); letter-spacing: .05em; text-transform: uppercase;
                   color: var(--faint); font-weight: 600; }
#basemapBar .val { color: var(--muted); min-width: 0; overflow-wrap: anywhere; }
#basemapBar .basemapown { margin-left: auto; }
#basemapBar .basemapown > summary { cursor: pointer; color: var(--link); font-size: var(--t-small); }
#basemapBar .basemapown[open] { flex: 1 1 100%; margin-left: 0; }
#basemapBar .basemapform { display: flex; flex-wrap: wrap; gap: var(--gap-2); margin-top: var(--gap-2); }
#basemapBar .basemapform input { flex: 1 1 260px; min-width: 0; }
/* The layer's drawing range, when the map is outside it: neutral, across the map's top edge. */
.visrangenote {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2) var(--gap-3);
  padding: var(--gap-2) var(--gap-4); background: var(--surface-2); border-bottom: 1px solid var(--rule);
  color: var(--ink); font-size: var(--t-small);
}
.visrangenote[hidden] { display: none; }
#mapPanel #legend { padding: var(--gap-3) var(--gap-4); }

/* ------------------------------------------------------------- the layer editor */

#editHead { display: flex; align-items: center; gap: var(--gap-4); margin-bottom: var(--gap-4); }
#editHead .crumbs, #mapCrumb { font-size: var(--t-body); color: var(--muted); }
#editHead .crumbs a, #mapCrumb a { color: var(--muted); }
#editHead .crumbs a:hover, #mapCrumb a:hover { color: var(--link); text-decoration: underline; }
#editHead .crumbs b, #mapCrumb b { color: var(--ink); font-weight: 600; }
/* The web map's crumb is the layer item's: muted, the place in ink, so it is not told apart by colour (round 2). */
#mapCrumb { margin-bottom: var(--gap-4); }
#editHead .right { margin-left: auto; display: flex; align-items: center; gap: var(--gap-3); }

#editDirty { font-size: 11.5px; font-weight: 600; color: var(--warn);
             border: 1px solid var(--warn-line); background: var(--warn-soft);
             border-radius: 3px; padding: 2px 8px; }
#editCancel { font-size: var(--t-small); color: var(--muted); }

/*
  A floor under the frame, so moving between a long page and a short one does not resize the panel
  and shift the left column out from under the pointer.
*/
#edit { display: grid; grid-template-columns: 196px 1fr; align-items: stretch; min-height: 440px;
        background: var(--surface); border: 1px solid var(--rule); border-radius: var(--round-lg);
        box-shadow: var(--lift); overflow: hidden; }
#editNav { border-right: 1px solid var(--rule); background: var(--surface-2); padding: var(--gap-2) 0; }
#editNav a { display: block; padding: 9px var(--gap-4); border-left: 3px solid transparent;
             color: var(--ink); font-size: var(--t-body); }
#editNav a:hover { background: var(--surface-3); text-decoration: none; }
#editNav a[aria-current] { background: var(--surface); border-left-color: var(--accent);
                           font-weight: 500; }

/*
  <b>The way to the other surface's pages for the same layer.</b> ADR-034 §5c promises that each
  surface links to the other's, so the split never means a dead end — a publisher hunting for
  Sharing in Server would otherwise find four pages and no clue. Set apart above a rule, because it
  leaves this environment and a link that does that should not look like a sibling.
*/
#editNav a.crossing { margin-top: 10px; padding-top: var(--gap-3);
                      border-top: 1px solid var(--rule); color: var(--muted);
                      font-size: var(--t-small); }
#editNav a.crossing:hover { color: var(--ink); }
#editNav a.crossing .in { display: block; font-size: 11px; color: var(--faint); }

/*
  <b>Two pages, one rule.</b> A service with no layers has a settings page too, and giving it its
  own padding is how two screens that should look identical start not to.
*/
#editPages, #servicePages, #servicePagesBody { padding: var(--gap-5); min-width: 0; }

/*
  <b>A service's settings wear the layer editor's frame.</b> D-61 moved them here; giving them their
  own grid and nav styling would be D-46 again in the same file, so `#serviceEdit` is `#edit` and
  `#serviceNav` is `#editNav` by selector rather than by copy.
*/
#serviceEdit { display: grid; grid-template-columns: 196px minmax(0, 1fr); align-items: stretch;
               overflow: hidden; }
#serviceNav { border-right: 1px solid var(--rule); background: var(--surface-2);
              padding: var(--gap-2) 0; }
#serviceNav a { display: block; padding: 9px var(--gap-4); border-left: 3px solid transparent;
                color: var(--ink); font-size: var(--t-body); }
#serviceNav a:hover { background: var(--surface-3); text-decoration: none; }
#serviceNav a[aria-current] { background: var(--surface); border-left-color: var(--accent);
                              font-weight: 500; }

/*
  <b>One column, because a settings page is a column.</b> The layer editor's pages are not bounded —
  they hold an endpoint list and a wall of field pills that want the width — but this page is four
  numbers and three sentences, and letting those run the full width put the prose in one part of the
  panel and the boxes in another.
*/
#servicePages { max-width: 780px; }

/*
  <b>One screen at a time, and both of these were missing for one build of the redesign.</b> All
  eight views are in the document and the router moves a class; dropping `.view` while rewriting the
  stylesheet stacked every screen into one page — the same symptom D-44 produced when a
  Content-Security-Policy blocked the whole sheet, and the same misreading: it looks like a layout
  bug rather than a missing rule.
*/
.view { display: none; }
.view.on { display: block; }

.page { display: none; }
.page.on { display: block; }

/*
  The summary of what is in force, as a footer to the page rather than a fourth row of the list
  above it: everything in it can be changed by the boxes, which is the opposite of the fixed facts.
*/
#limNote { margin-top: var(--gap-4); padding-top: var(--gap-3); border-top: 1px solid var(--rule); }

/*
  <b>The note under the service list is a tinted strip, which is what the reference does with it.</b>
  It was a paragraph of grey text at the bottom of a page, i.e. the shape of something nobody reads.
  A tint, a teal edge and an ⓘ make it a place rather than a leftover — and the tint is 4% of the
  accent, because a full-strength callout for a sentence about previews would be shouting.
*/
#serviceNote {
  display: flex; gap: var(--gap-3); align-items: flex-start;
  margin: var(--gap-4) 0 0; padding: var(--gap-3) var(--gap-4);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--round);
  color: var(--muted); max-width: none;
}
#serviceNote::before { content: "ⓘ"; color: var(--link); font-size: 14px; line-height: 1.3;
                       flex: none; }
#serviceNote:empty { display: none; }

/* -------------------------------------------------------------- dialog & drawer */

/*
  <b>One dialog, for a value that exists once.</b> The drawer below is for a form you fill and
  dismiss; this is for a secret you read and lose. `::backdrop` is the element's own, so there is no
  scrim div to keep in sync with anything.
*/
#issued { border: 1px solid var(--rule); border-radius: var(--round-lg); padding: var(--gap-5);
          max-width: 560px; background: var(--surface); color: var(--ink);
          box-shadow: 0 24px 60px -30px rgba(16, 24, 38, .5); }
#issued::backdrop { background: rgba(16, 24, 38, .42); }
/*
  <b>Not a section label, which is what the global `h4` rule had made it.</b> `h4, div.label, …` sets
  11.5px uppercase in `--faint`, so the one fact this dialog exists to get right — *who* the secret is
  for — rendered as chrome while the value nobody should mix up between people was 15px and bordered.
  Measured 2026-08-19. It is a heading here and is sized like one.
*/
#issued h4 { padding-bottom: var(--gap-2); border-bottom: 1px solid var(--rule);
              font-size: var(--t-head); text-transform: none; letter-spacing: 0;
              color: var(--ink); }
#issuedValue { width: 22ch; font-family: var(--mono); font-size: 15px; letter-spacing: .04em;
               text-align: center; }

/* ------------------------------------------------------------------ new item */
/*
  The New item dialog. Chrome first — a header, a scrolling body, a footer — because both of the
  owner's screenshots have exactly that and the body is the only part that scrolls: their *Create a
  feature layer* keeps Back and Next planted at the bottom with five options above them.
*/
#addItem { border: 1px solid var(--rule); border-radius: var(--round-lg); padding: 0;
           width: 680px; max-width: 94vw; background: var(--surface); color: var(--ink);
           box-shadow: 0 24px 60px -30px rgba(16, 24, 38, .5); }
#addItem::backdrop { background: rgba(16, 24, 38, .42); }

/* Only the screen that holds a table, and it goes back to 680px on the way out. */
#addItem.wide { width: 980px; }

/*
  <b>The table scrolls inside its own box, and the page never does.</b> A geodatabase's feature-class
  names run to forty characters and there are five columns; on a narrow window that is wider than the
  dialog, and a body that scrolls sideways takes the footer with it.
*/
.widetable { overflow-x: auto; }
.widetable table { min-width: 640px; }
/* A panel that scrolls keeps its own padding rather than losing it to the scroll box. */
.panel.widetable { padding-bottom: 0; }

/*
  <b>Below 720 pixels a row is a block, not a strip you scroll sideways.</b> The UX sweep of 2026-10-19 measured
  every Studio table at 390: Groups hid Members and Services, Members hid User type, Role and the ⋯, and nothing
  said the columns were there. A `table.rowblocks` row becomes its name first, the ⋯ beside it, and each other
  cell as a labelled line underneath — the label is the cell's `data-l`, so the heading it lost is said again.
  The name cell is `td.name` or `td.lead`, the actions cell `td.act`, a selection box `td.tick`.
*/
@media (max-width: 719px) {
  table.rowblocks, .widetable table.rowblocks, #view-roles .panel table.rowblocks,
  #view-groups .panel table.rowblocks { min-width: 0; }
  .widetable:has(> table.rowblocks), .panel:has(> table.rowblocks) { overflow-x: visible; }
  table.rowblocks thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  table.rowblocks tbody { display: block; }
  table.rowblocks tr { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
                       gap: 4px var(--gap-3); padding: var(--gap-3) var(--gap-4);
                       border-bottom: 1px solid var(--rule); }
  table.rowblocks tr:last-child { border-bottom: 0; }
  table.rowblocks td { display: block; padding: 0; border: 0; min-width: 0; text-align: left; }
  table.rowblocks td:is(.name, .lead) { grid-column: 1 / -2; grid-row: 1; }
  table.rowblocks td:is(.tick, .thumbcell) { grid-column: 1; grid-row: 1; width: auto; }
  table.rowblocks td:is(.tick, .thumbcell) + td:is(.name, .lead) { grid-column: 2 / -2; }
  /* An unbroken login, an e-mail address from a provider, wraps inside its block rather than pushing the page wide. */
  table.rowblocks td:is(.name, .lead), table.rowblocks td:is(.name, .lead) * { overflow-wrap: anywhere; }
  table.rowblocks .whorow > span { min-width: 0; }
  table.rowblocks td.tick input { width: 24px; height: 24px; margin: 0; }
  table.rowblocks td.thumbcell .thumb { width: 72px; height: 48px; }
  table.rowblocks td[data-l] { grid-column: 1 / -1; display: flex; gap: var(--gap-2); align-items: baseline;
                               font-size: var(--t-small); }
  table.rowblocks td[data-l]::before { content: attr(data-l); flex: 0 0 96px; color: var(--muted); }
  table.rowblocks td[data-l] > * { min-width: 0; }
  table.rowblocks td.empty, table.rowblocks tr.groupband > td { grid-column: 1 / -1; }
  /* The ⋯ or the row's button keeps the top right, and is not the sticky pinned column the wide rule makes. */
  table.rowblocks td.act, .widetable table.rowblocks td.act:last-child {
    grid-column: -2 / -1; grid-row: 1; align-self: start; position: static; box-shadow: none;
    background: transparent; text-align: right;
  }
  :root[data-theme="dark"] .widetable table.rowblocks td.act:last-child { background: transparent; }
  table.rowblocks td:empty { display: none; }
}

/*
  The route table's two problems: a pattern that cannot wrap because it has no spaces, and a last
  column starved by the first taking everything. Breaking at the slashes gives the pattern somewhere
  to fold, and holding the governance clause on one line stops a 67-row table becoming 9,700px.
*/
/*
  Overview's two columns: the layer list, and the details beside it. `minmax(0, 1fr)` on the first for
  the reason the services grid needed it — a bare `1fr` cannot shrink below its content's min-content and
  a table's min-content is wide. Stacks below 1100px, where a 300px aside beside a five-column table is
  two things squeezed rather than two things side by side.
*/
.itemgrid { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--gap-5);
            align-items: start; }
@media (max-width: 1100px) { .itemgrid { grid-template-columns: minmax(0, 1fr); } }

/* The left column stacks: what the item is, then what is in it. */
.itemmain { display: flex; flex-direction: column; gap: var(--gap-5); min-width: 0; }

/* The address, readable and copyable in one row. The field is wide and the button does not shrink. */
.urlrow { display: flex; gap: var(--gap-2); align-items: center; }
.urlrow input { flex: 1 1 auto; min-width: 0; font-family: var(--mono); font-size: var(--t-small);
                background: var(--surface-2); }

/* A group layer's glyph, and the indent that says a layer sits under it. `parentLayerId` is in the
   service document and the table was ignoring it, so a nested layer read as a sibling. */
.rowicon { display: inline-flex; vertical-align: -3px; margin-right: 6px; color: var(--muted); }
.rowicon svg { width: 16px; height: 16px; }
td.name.nested { padding-left: 36px; }

/* The item's own header: the preview the content list already generates, beside what the item is. */
.itemhead { display: flex; gap: var(--gap-4); align-items: flex-start; }
.itemhead > div { min-width: 0; }
.itemhead b { font-size: var(--t-head); }
.itemhead .hint { margin: var(--gap-2) 0 0; }

/*
  <b>Three tiers, where there were two of the same.</b> The name is `--t-head`, the subtitle
  `--t-small` in `--muted`, and the dates were also `--t-small` in `--muted` — so the second and third
  lines read as one grey block. `--faint`'s own note says what it is for: *a second fact inside a cell
  whose first fact is already muted*. One size down and one step of grey, both from the scale.
*/
.footnote { margin-top: var(--gap-1); font-size: var(--t-label); color: var(--faint); }

/*
  <b>Bigger only here.</b> 104×70 is right beside a table row and reads as a mistake beside three lines
  of text with nothing else in the panel. 168×112 is the same ratio and the height of the text block it
  sits next to. Scoped, because the base rule also draws every thumbnail on the content list.
*/
#itemThumb .thumb { width: 168px; height: 112px; }

/* The section heading above the layer panel, where a header row used to be. Same treatment as the
   aside's own headings, so the page names a section one way. */
.itemmain > h4 { margin: 0; }
.urlrow button { flex: 0 0 auto; }

/*
  <b>A pill that is a button, and looks like the pill it replaced.</b> ADR-034 §5l makes the sharing
  scope on a content row the control that changes it — the reader was going to press it anyway. So the
  button carries no chrome of its own and the pill inside keeps its hue, its glyph and its border; what
  it gains is a cursor, a focus ring, and a real keyboard path, which a `span` had none of.
*/
.pillbtn { background: none; border: 0; padding: 0; margin: 0; font: inherit; cursor: pointer; }
.pillbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 999px; }

/* The groups an item is shared with, as removable chips. Small, because the list is usually short and a
   table of two rows is a table for the sake of one. */
.chips { display: flex; flex-wrap: wrap; gap: var(--gap-2); margin: var(--gap-2) 0 var(--gap-3); }
.chip { display: inline-flex; align-items: center; gap: var(--gap-1);
        padding: 3px 4px 3px 10px; border: 1px solid var(--rule-strong); border-radius: 999px;
        background: var(--surface-2); font-size: var(--t-small); }
.chip button { line-height: 1; padding: 2px 5px; }

/*
  <b>The geodatabase screens, and both rules are borrowed from tables above rather than invented.</b>

  `.gdbpick` is the selection table: five short, controlled-vocabulary columns beside one column of
  feature-class names fifty characters long. The short ones do not wrap, for the reason `#services`
  gives — `MultiPolygon Z` was breaking in two while `MultiPolygon` did not, and measured row heights
  came out 77.5, 77.5, 54.25, 77.5, 54.25 down the same table. A ragged table reads as a bug.

  `.gdbreport` is the per-layer outcome, where a refusal arrives as whatever the datastore said. A
  provider message can carry a sixty-character identifier with no space in it, which sets a hard
  minimum on its column and pushes the table past the dialog — measured at 1440×900, not only on a
  small screen. `overflow-wrap: anywhere` is what `.routetable` already does with a long path.
*/
.gdbpick td:not(:nth-child(2)), .gdbpick th:not(:nth-child(2)) { white-space: nowrap; }
.gdbreport td:last-child { overflow-wrap: anywhere; }
.gdbreport td:nth-child(2) { white-space: nowrap; }

.routetable td:first-child { overflow-wrap: anywhere; }
.routetable td:last-child { white-space: nowrap; }
.routetable table { min-width: 780px; }

/*
  <b>A row's primary cell carries the real control.</b> A clickable `<tr>` is a mouse affordance and
  nothing else: the roles table had no keyboard path to its own function at all — tabbing from *New
  role* skipped all five rows and landed in the editor below. So the name is a button where the row
  opens something on this page, and a link where the row is an address. It is styled as the text it
  replaced, because it is the row's name and not a second control beside it.
*/
.rowname { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit;
           font-weight: inherit; text-align: left; cursor: pointer; }
.rowname:hover { text-decoration: underline; }
.rowname:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
/*
  <b>Only where the whole row is clickable.</b> This was written for `tr.pick`, where a coloured link
  would compete with the row's own affordance — and applied to every `td.name a`, including the service
  page's Overview, whose rows are *not* clickable. Measured 2026-08-19: the link `look_EarlyAlert_sites`
  and the plain label `Reports` rendered identically, `--ink` at weight 600 with no underline, so the
  only signal that one was a link and the other was not was an absence elsewhere in the row.
*/
tr.pick td.name a { color: inherit; text-decoration: none; }
tr.pick td.name a:hover { text-decoration: underline; }

/* And everywhere else a link in a name cell looks like one. */
td.name a { color: var(--link); text-decoration: none; }
td.name a:hover { text-decoration: underline; }

.dialoghead { display: flex; align-items: center; justify-content: space-between;
              gap: var(--gap-4); padding: var(--gap-4) var(--gap-5);
              border-bottom: 1px solid var(--rule); }
.dialoghead h3 { font-size: var(--t-head); margin: 0; min-width: 0; }
/* A long title wraps beside the close x, never pushing it to a line of its own (verification round, 2026-10-19). */
.dialoghead { flex-wrap: nowrap; height: auto; }
.dialoghead > button { flex: none; }
/* The close control is one drawn x in the muted ink, on every step: it was a dingbat, blue on one step of New item and
   rust on the next (UX sweep 2026-10-19). */
.dialoghead > button[aria-label="Close"] { color: var(--muted); }
.dialoghead > button[aria-label="Close"]:hover { color: var(--ink); }
.dialoghead > button:has(> .closex) { padding: 5px; line-height: 0; }
.dialoghead .closex { width: 16px; height: 16px; display: block; }

/* Capped rather than tall: the dialog must not grow past the viewport on a laptop, and their
   screen 2 is already 900 pixels of options. */
.dialogbody { padding: var(--gap-5); max-height: min(68vh, 620px); overflow-y: auto; }
#serviceFacts, #resourceWidget { min-width: 0; overflow-wrap: anywhere; }
/* The body is what gives way when the foot grows: a consequence sentence in the foot pushed Cancel and the
   button below the dialog's edge (sweep 2026-10-19, round 3). The dialog keeps the browser's own height cap. */
dialog[open]:has(> .dialogbody) { display: flex; flex-direction: column; }
dialog > .dialogbody { flex: 0 1 auto; min-height: 0; }
dialog > .dialoghead, dialog > .dialogfoot { flex: none; }

.dialogfoot { display: flex; align-items: center; gap: var(--gap-2);
              padding: var(--gap-4) var(--gap-5); border-top: 1px solid var(--rule);
              background: var(--surface-2); border-radius: 0 0 var(--round-lg) var(--round-lg); }
.dialogfoot .fill { flex: 1; }
/* A footer that says what its button does: the sentence takes the first line, the buttons the next. */
.dialogfoot.withsays { flex-wrap: wrap; }
.dialogfoot.withsays > .consequence, .dialogfoot.withsays > .bad-inline { flex: 1 1 100%; margin: 0 0 var(--gap-2); }
.dialogfoot.withsays > .bad-inline[hidden] { display: none; }
/* Empty on the first screen, where the only way out is the close button — theirs is the same. */
.dialogfoot:empty { display: none; }

/*
  The drop zone. Dashed, because a dashed edge is the one border convention that reads as *put
  something here* rather than *this is a box*, and it is the one their screen uses.
*/
.dropzone { border: 1px dashed var(--control-edge); border-radius: var(--round-lg);
            background: var(--surface-2); padding: var(--gap-6) var(--gap-5);
            text-align: center; display: grid; justify-items: center; gap: var(--gap-3); }
.dropzone p { margin: 0; font-size: var(--t-body); }
.dropzone .val { font-size: var(--t-small); color: var(--faint); }
.dropzone svg { width: 30px; height: 30px; color: var(--faint); }
/* Only while a file is over it: the affordance has to answer, or a drag ends in a guess. */
.dropzone.over { border-color: var(--accent); background: var(--accent-soft); }
.dropzone.over svg { color: var(--link); }

/* Two columns, as theirs, collapsing rather than squeezing — a 48px icon beside two lines of prose
   has no room to give at 520px. */
.newtiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: var(--gap-4); margin-top: var(--gap-5); }
@media (max-width: 620px) { .newtiles { grid-template-columns: minmax(0, 1fr); } }

/*
  <b>One tile spans, and the rule retires itself.</b> The design review's answer to *does a single
  tile in a two-column grid read as room to grow* was no — it reads as a layout that half-rendered,
  because an equal-width void beside it carries no signal of intent. `:only-child` spans it today and
  stops applying the moment there is a second tile, so the two-column mechanism stays for when
  ADR-034 §5j's grid has something to put in it.
*/
.newtiles > .newtile:only-child { grid-column: 1 / -1; }

/*
  The *or* between the drop zone and the tiles. A rule with a word sitting in it, because the two
  halves of screen one are alternatives and nothing in the layout said so.
*/
.orbar { display: flex; align-items: center; gap: var(--gap-3); margin: var(--gap-5) 0 0;
         font-size: var(--t-small); color: var(--faint); }
.orbar::before, .orbar::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

/* The heading takes focus on every screen change, so it must not draw a ring as though it were a
   control. The screen reader hears it; the eye should not be told to click it. */
#addItemTitle:focus { outline: none; }
#addItemTitle:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.newtile { display: grid; grid-template-columns: 46px minmax(0, 1fr); gap: var(--gap-3);
           align-items: start; text-align: left; padding: var(--gap-3); cursor: pointer;
           border: 1px solid transparent; border-radius: var(--round); background: none;
           font: inherit; color: inherit; width: 100%; }
.newtile:hover { border-color: var(--rule-strong); background: var(--surface-2); }
.newtile .glyph { display: grid; place-items: center; width: 46px; height: 46px;
                  background: var(--surface-3); border-radius: var(--round); color: var(--muted); }
.newtile .glyph svg { width: 22px; height: 22px; }
.newtile b { display: block; font-weight: 600; }
.newtile span { display: block; margin-top: 2px; font-size: var(--t-small); color: var(--muted); }

/*
  The radio list. One accent, and it is the one this console already uses for a chosen thing — the
  selected option gets the accent edge and a tinted ground, which is how the rest of the page marks
  a current row.
*/
.pickrow { display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: var(--gap-3);
           padding: var(--gap-4); border: 1px solid var(--rule); border-radius: var(--round);
           margin-top: var(--gap-2); cursor: pointer; }
.pickrow:first-of-type { margin-top: 0; }
.pickrow:hover { border-color: var(--rule-strong); }
.pickrow.on, .pickrow:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.pickrow input { margin: 3px 0 0; accent-color: var(--accent); }
.pickrow input[type="radio"] { width: 18px; height: 18px; }
.pickrow:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.pickrow b { display: block; font-weight: 600; }

/*
  <b>16 pixels, because without this they were 573.</b> `icon()` returns a bare `svg` with no dimensions
  — every other caller sizes it (`.pill svg`, `.rail-item svg`, `.dropzone svg`, `.newtile .glyph svg`)
  and the Share dialog's scope rows did not, so a single padlock filled the dialog and pushed the rest of
  the screen out of view. Measured by the design review 2026-08-19: 573.47 × 573.47, three times. This is
  what the owner was looking at when they asked whether a designer had done it.
*/
.pickrow svg { width: 16px; height: 16px; flex: none; vertical-align: -2px; }

/*
  <b>The description has its own class, because a bare descendant selector matched both spans.</b>
  `.pickrow span` also hit the wrapper around the `b`, so the scope's label and the sentence explaining it
  rendered at the same 13.5px in the same muted grey — no hierarchy between *what you are choosing* and
  *what it means*. The same markup is New item's route picker, so both were affected.
*/
.pickrow .lede { display: block; margin: 3px 0 0; font-size: var(--t-small); color: var(--muted); }
/* The global `.lede` gives a paragraph 16 pixels under it, which left every card bottom-heavy; and in a dialog the
   cards are a step tighter so the choices fit above the fold (verification round, 2026-10-19). */
dialog .pickrow { padding: var(--gap-3) var(--gap-4); }

/* Dialog text is `--ink`, not the browser's default black. Nothing was overriding the UA rule, so every
   dialog's base colour was a coincidence rather than the token. */
dialog { color: var(--ink); }
/* Named as a question, because their heading is one: *Select an option to create an empty feature
   layer.* A list of five nouns with no question above it is a list of five nouns. */
.picklede { margin: 0 0 var(--gap-4); font-size: var(--t-body); }

#drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: 620px; max-width: 96vw;
  background: var(--surface); border-left: 1px solid var(--rule); z-index: 1500;
  transform: translateX(100%); transition: transform .16s ease-out;
  display: flex; flex-direction: column;
  box-shadow: -18px 0 44px -30px rgba(16, 24, 38, .45);
}
#drawer.on { transform: none; }
/* Closed, the drawer is only moved off-screen, so its shadow bled into every page's right edge —
   visible once the sign-in page had nothing else on it. Hidden after the slide, not before it. */
#drawer:not(.on) { box-shadow: none; visibility: hidden; transition: transform .16s ease-out, visibility 0s .16s; }
#drawer header { height: auto; padding: var(--gap-4) var(--gap-5); align-items: flex-start;
                 gap: var(--gap-3); position: static; }
#drawer header h2 { margin: 0; font-size: var(--t-head); font-weight: 600; }
#drawerBody { overflow-y: auto; padding: var(--gap-5); flex: 1; }

/* ---------------------------------------------------------------------- toast */

#toast {
  position: fixed; right: var(--gap-5); bottom: var(--gap-5); max-width: 480px; z-index: 2000;
  background: var(--ink); color: #fff; font-size: var(--t-small);
  padding: 11px var(--gap-4); border-radius: var(--round);
  box-shadow: 0 18px 40px -22px rgba(16, 24, 38, .6);
  opacity: 0; transform: translateY(6px); pointer-events: none;
  transition: opacity .14s, transform .14s;
}
#toast.on { opacity: 1; transform: none; }
#toast.bad { background: var(--alert); }
#toast.good { background: var(--ok); }

/* ═══════════════════════════════════════════════════════════════════════ a phone, D-273
   <b>Below 760 pixels the sidebar is the rail, whatever the reader chose.</b> Measured 2026-09-16 at
   390 px: the 232-pixel navigation column stayed, which left 158 pixels of work, and a page heading's
   sentence broke after every word. The collapsed state already exists and is drawn — the toggle has
   chosen it since the console was written — so this is that state made the only one where a wider one
   does not fit, rather than a second narrow design nobody reviews.

   <b>The toggle goes with it.</b> A control whose two states look identical is a control that teaches a
   reader it does nothing; the rail is the only width here, so there is nothing to toggle.

   <b>Everything else is the same screens.</b> The panes that give up a column already do so at 1,100 and
   1,180; what is added here is the frame around them — the heading stacks, the toolbars wrap, the
   toast spans the width instead of sitting under the thumb's corner, and the work's side padding drops
   to the gutter these pages use everywhere else. */
@media (max-width: 760px) {
  /* A sticky header of three rows is a quarter of a phone's height on every page; it scrolls away. */
  header { position: static; }

  .wrap { padding: var(--gap-4) var(--gap-3); }

  /* The heading and its controls are a column: side by side, the sentence broke after every word. */
  .pagehead { flex-direction: column; gap: var(--gap-3); }
  .pagehead .right { align-self: stretch; }
  .pagehead .segmented { align-self: flex-start; }

  /* A pair of fields is one field; `.grid2` is the form idiom these screens share. */
  .grid2 { grid-template-columns: minmax(0, 1fr); max-width: none; }

  /* The layer and service editors put their navigation above their page rather than beside it. */
  /* <b>Both editors, as the comment says.</b> Only the layer's selectors were here until the design review
     of 2026-09-24 found the service's navigation still a column at 390 pixels, its page 200 wide and the
     page size box cut off at the edge. `#serviceEdit` is `#edit` by selector everywhere else in this file. */
  #edit, #serviceEdit { grid-template-columns: minmax(0, 1fr); }
  #editNav, #serviceNav { border-right: 0; border-bottom: 1px solid var(--rule); display: flex; overflow-x: auto; }
  #editNav a, #serviceNav a { border-left: 0; border-bottom: 3px solid transparent; white-space: nowrap; }
  #editNav a[aria-current], #serviceNav a[aria-current] { border-left-color: transparent; border-bottom-color: var(--accent); }
  #editPages, #servicePages, #servicePagesBody { padding: var(--gap-4) var(--gap-3); }

  /* Full width, because 480 pixels of toast beside a 16-pixel margin is the width of the screen. */
  #toast { left: var(--gap-3); right: var(--gap-3); bottom: var(--gap-3); max-width: none; }

  /* <b>The top bar wraps, and Sign out comes back on screen.</b> Both phone reviews found it first and
     measured the same thing: `header` is a 58-pixel row that does not wrap, its content is 432 pixels
     wide in a 334-pixel column, so `#who` and Sign out sat off the right edge — and the page scrolled
     98 pixels sideways to reach them, which drags the rail off with it. The health line takes its own
     row and truncates rather than wrapping to five lines over the breadcrumb. */
  header:not(.dialoghead) { height: auto; min-height: 52px; flex-wrap: wrap; row-gap: 4px;
           padding: var(--gap-2) var(--gap-3); gap: var(--gap-3); }
  #healthLine { flex: 1 1 100%; order: 3; min-width: 0;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  #who { max-width: 40vw; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  #who b { display: inline; margin-right: 6px; }

  /* A toolbar is a row of filters; at this width it is a column of them, and the search box takes the
     line it needs. On Groups the New button drew over the row count — `no gro⟨button⟩`. */
  .toolbar { flex-wrap: wrap; }
  .toolbar input[type=search], .toolbar input[type=text] { flex: 1 1 100%; min-width: 0; }

  /* <b>A tab strip scrolls rather than losing its last tab.</b> `.segmented` is `flex: none` and
     `nowrap`, so `#serviceTabs` at 405 pixels in a 302-pixel column cut Settings off entirely, and
     `#visModes` cut Tiles — the tile preview could not be chosen at all. */
  .segmented { flex: 0 1 auto; min-width: 0; max-width: 100%;
               overflow-x: auto; scrollbar-width: none; }
  .pagehead .segmented { align-self: stretch; }

  /* Two tables are not in `.widetable`, so they pushed their own card open instead of scrolling in it. */
  #view-roles .panel, #view-sources .panel { overflow-x: auto; }
  #view-roles .panel table, #view-sources .panel table { min-width: 460px; }

  /* The basemap bar: Use and None were off the edge, so a basemap could be neither set nor cleared. */
  #basemapBar .val { flex: 1 1 100%; }
  #basemapBar .basemapown { margin-left: 0; }
  #basemapInput { flex: 1 1 100%; min-width: 0; }
  #basemapSet, #basemapClear { flex: 1 1 auto; }

  /* <b>Forty pixels for anything a finger presses.</b> Every row control was 20 to 28 pixels tall. This
     is the touch target only — the type size and the spacing between rows are unchanged. */
  .tiny, .pillbtn, .menu > summary, #surfaces a, .segmented a, .segmented button, #editNav a {
    min-height: 40px; display: inline-flex; align-items: center; justify-content: center;
  }
  #editNav a { justify-content: flex-start; flex: 0 0 auto; }

  /* The symbology strip keeps the page's gutter, and its state sentence is read rather than cut. */
  .symstrip { padding: 0 var(--gap-3); }
  .symstrip .symstate { white-space: normal; }

  /* <b>A row menu is a sheet at the bottom.</b> It was absolutely positioned and 450 pixels wide inside
     a 310-pixel scroller: opening it looked like nothing happening, with the sheet at x 461 and Delete
     past the edge — the clipping `.menu .sheet`'s own comment predicted. The script stops positioning
     it below this width, so these rules are the whole of it: across the bottom, where a thumb is, and
     scrolling inside itself when it is tall. */
  .menu[open] .sheet { position: fixed; left: var(--gap-3); right: var(--gap-3);
                       top: auto; bottom: var(--gap-3); width: auto; min-width: 0;
                       max-height: 60vh; overflow-y: auto; }
  .menu .sheet button, .menu .sheet a { min-height: 40px; display: flex; align-items: center; }

  /* The note under a destructive item is a sentence; held on one line it read *…never remove‸*. */
  .menu .sheet .note { white-space: normal; }

  /* <b>The actions stay where the thumb is.</b> Reaching the row menu on the services table cost a
     550-pixel sideways swipe, and the column it is in is the one every row is opened from. The shadow
     is the edge that says the other columns pass under this one rather than ending there. */
  .widetable table td:last-child, .widetable table th:last-child {
    position: sticky; right: 0; z-index: 1; background: var(--surface);
    box-shadow: -10px 0 10px -10px rgba(16, 24, 38, .25);
  }

  /* <b>The open menu's own cell comes forward.</b> A sticky cell makes a stacking context, so the
     fixed sheet is trapped inside the cell it is in and every later row's pinned cell paints over it —
     measured with `elementFromPoint`: a *Map* button of the row below sat on top of *The layer
     document*. Raising the sheet does nothing; raising its cell is what works. */
  .widetable table td:last-child:has(.menu[open]) { z-index: 3; }

  /* The row under the pointer keeps its highlight in the pinned column too, rather than showing a
     white patch where the later rule wins. */
  tr.pick:hover td:last-child { background: var(--surface-2); }

  /* <b>Not on the logs, where the last column is the resource and pinning it is a column of dashes.</b>
     There is no row menu there, so there is nothing to keep in reach. */
  #view-logs .widetable table td:last-child, #view-logs .widetable table th:last-child {
    position: static; box-shadow: none;
  }

  /* Two more scope strips that are not `.segmented`, and two more tables that are not in `.widetable`. */
  .tabstrip { overflow-x: auto; scrollbar-width: none; }
  .tabstrip a { flex: 0 0 auto; white-space: nowrap; min-height: 40px;
                display: inline-flex; align-items: center; gap: 6px; }
  /* A flex row drops the space before the count, which read "Content1" (sweep 2026-10-19); the gap puts it back. */
  #view-groups .panel { overflow-x: auto; }
  #view-groups .panel table { min-width: 460px; }

  /* <b>An empty state is a sentence, not a table.</b> Dropping the minimum width was not enough: the
     global `th { white-space: nowrap }` still sized the table from a header with nothing under it, so
     the sentence was clipped at 504 pixels in a 308-pixel panel. With no rows to label, the header is
     not a header. */
  #view-groups .panel table:has(td.empty) { min-width: 0; }
  #view-groups .panel table:has(td.empty) thead { display: none; }

  /* The controls the first pass missed, measured at 20 to 34 pixels. */
  /* `td a:has(.pill)` is the visibility link, which is a pill inside a link and was 20 pixels tall; the
     first version of this rule named a class the markup does not have. */
  .rowname, #signout, #logMore, td a:has(.pill) { min-height: 40px;
    display: inline-flex; align-items: center; }
}

/* ------------------------------------------------------------- the two card forms */

/*
  <b>Two panels, one set of rules.</b> The forced password change is the same shape as signing in —
  a narrow centred card of labelled fields — and its first version got none of this because the
  selectors named `#signin`. It rendered as labels and boxes side by side, spilling out of the card
  (D-46 again, in the same file as instance 8).
*/
#signin, #mustchange { max-width: 380px; margin: 72px auto; }
#signin .panel, #mustchange .panel { padding: var(--gap-5); }
#signin h2, #mustchange h2 { margin: 0 0 var(--gap-2); font-size: 19px; font-weight: 600; }
#signin label, #mustchange label { display: block; font-size: var(--t-small); color: var(--muted);
                                   margin: var(--gap-3) 0 var(--gap-1); }
#signin input, #signin button.primary,
#mustchange input, #mustchange button { width: 100%; }
#signin button.primary, #mustchange button { margin-top: var(--gap-4); }

/*
  <b>The sign-in card stands alone, the way the portal's does.</b> Owner, 2026-09-25, with the two
  screenshots side by side. Until there is a session the rail and the top bar are hidden: every
  link in them needs one, and the health line could only ever say *sign in to read the server's
  state*. The attribute is the same one the head script sets before first paint, so the card is
  alone from the first frame rather than after the script decides.
*/
html[data-session="none"] #main > header { display: none; }
#signin { width: 100%; max-width: 420px; margin: clamp(32px, 12vh, 120px) auto 48px; padding: 0 var(--gap-4); }
#signin .panel { padding: 0; overflow: hidden; }
#signin .signin-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--gap-3);
  padding: var(--gap-4) var(--gap-5); border-bottom: 1px solid var(--rule);
}
#signin .signin-head h2 { margin: 0; font-size: 20px; }
#signin .signin-where { margin: 2px 0 0; font-size: var(--t-small); color: var(--muted);
                        overflow-wrap: anywhere; }
/* The header's mark, the same blue circle: it was the old navy square (UX sweep 2026-10-19). */
#signin .signin-head .mark {
  width: 34px; height: 34px; flex: none; border-radius: 50%;
  background: var(--accent);
  display: grid; place-items: center; color: #fff; font-size: 15px; font-weight: 700;
}
#signin .signin-head .mark svg { width: 18px; height: 18px; display: block; }
#signin .signin-body { padding: var(--gap-2) var(--gap-5) var(--gap-5); }
#signin .signin-for { margin: var(--gap-3) 0 0; font-size: var(--t-small); color: var(--ink); overflow-wrap: break-word; }
#signin .signin-stranger a { text-decoration: underline; text-underline-offset: 2px; }
#signin .signin-stranger {
  margin: 0; padding: var(--gap-3) var(--gap-5); border-top: 1px solid var(--rule); background: var(--surface-2);
  font-size: var(--t-small); color: var(--muted); text-align: center;
}
#signin #u, #signin #p { font-size: 15px; min-height: 42px; padding: 8px 10px; }
/* ADR-204 §5e item 16: the field's own note, so it sits tight under the field and the next label keeps its gap. */
#signin .signin-other { margin: var(--gap-1) 0 0; }
#signin .signin-other code { white-space: nowrap; }
#signin #p { padding-right: 68px; }
#signin .signin-refused {
  margin: var(--gap-3) 0 0; padding: 10px 12px; font-size: var(--t-small);
  background: var(--warn-soft); color: var(--warn); border: 1px solid var(--warn-line);
  border-radius: 7px;
}

/* Show sits inside the field, at its end, and says what it will do rather than what it is. */
#signin .reveal { position: relative; }
#signin .reveal-toggle {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: auto; margin: 0; padding: 6px 10px; min-height: 32px;
  background: none; border: 0; color: var(--link); font-weight: 600; font-size: var(--t-small);
}
#signin .reveal-toggle:hover { text-decoration: underline; }

#signin label.keep {
  display: inline-flex; align-items: center; gap: 8px; margin: var(--gap-3) 0 0;
  color: var(--ink); font-size: var(--t-body); cursor: pointer;
}
#signin label.keep input { width: 18px; height: 18px; margin: 0; flex: none; }

#signin .forgot {
  display: block; width: auto; margin: var(--gap-3) auto 0; padding: 6px 4px; min-height: 32px;
  background: none; border: 0; color: var(--link); font-size: var(--t-small);
  text-decoration: underline; text-underline-offset: 3px;
}
#signin #forgotHint { margin: var(--gap-2) 0 0; text-align: center; }

/*
  <b>The conversion's losses, and a derived document beside them.</b> ADR-033 accepted a
  lossy projection onto the ArcGIS face and the mitigation is that it says what it lost —
  so this reads as a list of statements, not as an error state. Amber rather than red: none
  of it is broken, and a page that cried wolf about a dashed line would train its reader to
  stop looking at the one that matters.
*/
.losses {
  margin: 6px 0 0; padding: 0 0 0 18px;
  color: var(--ink); font: var(--t-small)/1.55 var(--sans);
}
.losses li { margin: 4px 0; }
.losses li::marker { color: var(--warn-dot); }

#symLoss {
  margin-top: 14px; padding: 12px 14px;
  border: 1px solid var(--rule); border-left: 3px solid var(--warn-dot);
  border-radius: var(--round); background: var(--surface);
}
#symLoss h4 { margin: 0; }

/* The colours an ArcGIS client will use, D-99. Above the document rather than beside it:
   the colour is the answer and the JSON is the evidence, and a reader who wants the quad
   has it on the chip's title. */
.swatches {
  display: flex; flex-wrap: wrap; gap: var(--gap-2);
  margin: 8px 0 0;
}
.swatches .swatch {
  /* <b>Stated, because a rule elsewhere states it.</b> `.swatch` at the top of this file is a
     9x9 status dot and it is the only rule that declares a width — so without these two lines
     it wins the cascade here and every legend chip collapses to nine pixels while its patch and
     its label spill out over the next one. Measured on `ci_many`'s three-class legend. */
  width: auto; height: auto;

  display: inline-flex; align-items: center; gap: var(--gap-2);
  padding: 4px 10px 4px 4px;
  border: 1px solid var(--rule); border-radius: var(--round);
  background: var(--surface); color: var(--ink);
  font: var(--t-small)/1.4 var(--sans);
}
/* A checkerboard behind the patch, so a half-transparent fill reads as half transparent
   rather than as a paler colour. Drawn in the panel's own tones, so it is a texture rather
   than a second palette. */
.swatches .patch {
  width: 18px; height: 18px; flex: 0 0 18px; border-radius: 3px;
  border: 2px solid transparent;
  background-color: var(--surface);
  background-image:
    linear-gradient(45deg, var(--rule) 25%, transparent 25%, transparent 75%, var(--rule) 75%),
    linear-gradient(45deg, var(--rule) 25%, transparent 25%, transparent 75%, var(--rule) 75%);
  background-size: 8px 8px;
  background-position: 0 0, 4px 4px;
  box-shadow: inset 0 0 0 1px var(--rule);
  position: relative; overflow: hidden;
}
.swatches .patch .fill { position: absolute; inset: 0; }

/* D-99: the shape a client will see. A line is a band across the chip rather than a filled
   square, and a point is a dot in the middle of one — both drawn from the same colour, so a
   reader compares colours across shapes without having to discount the shape. */
.swatches .patch.stroke .fill { inset: 6px 0; border-radius: 1px; }
.swatches .patch.mark .fill { inset: 4px; border-radius: 50%; }

/* A document this server derived, shown as output because that is what it is. */
pre.doc {
  margin: 8px 0 0; padding: 12px 14px; overflow: auto; max-height: 320px;
  border: 1px solid var(--rule); border-radius: var(--round);
  background: var(--surface); color: var(--ink);
  font: 12px/1.5 var(--mono); white-space: pre;
}

/* The roles editor — ADR-035 §4f, two sections of grouped ticks. Dense on purpose: eighteen
   privileges today and more later, and an operator comparing two roles reads them as a list. */
.rolesection { margin-top: var(--gap-4); }
.rolesection h4 { display: flex; align-items: center; gap: var(--gap-3); margin-bottom: var(--gap-2); }
.rolesection h4 .val { font-weight: 400; }
.rolesection h4 button { margin-left: auto; }
.rolegroup { border: 1px solid var(--rule); border-radius: var(--round-lg);
             padding: var(--gap-3); margin-bottom: var(--gap-3); background: var(--surface); }
.rolegrouphead { display: flex; align-items: center; gap: var(--gap-3);
                 font-weight: 600; font-size: var(--t-small); margin-bottom: 6px; }
/* One privilege, one row (UX sweep 2026-10-19): an 18-pixel box, its name in words, the identifier small, the
   user-type pill, and the sentence underneath. The whole row is the label, so the target is the row. */
.roleprivilege { display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: var(--gap-3); align-items: start;
                 padding: var(--gap-2) var(--gap-2); border-radius: var(--round); cursor: pointer; }
.roleprivilege:hover { background: var(--surface-2); }
.roleprivilege input[type="checkbox"] { width: 18px; height: 18px; margin: 1px 0 0; accent-color: var(--accent); }
.roleprivilege .privilegename { font-weight: 600; }
.roleprivilege code.mono { font: 12px/1.3 var(--mono); color: var(--faint); background: none; padding: 0; }
.roleprivilege .tierpill::before { display: none; }
.roleprivilege .tierpill.cappednote { background: var(--warn-soft); border-color: var(--warn-line); color: var(--warn); }
.roleprivilege input[disabled] + .privilegetext .privilegename { color: var(--muted); }

/* <b>Capped by a user type: muted, not disabled.</b> The 1c handoff asks for these greyed with a
   note, and the prototype greys the *text* and leaves the control — which is the honest reading.
   The role does carry the privilege and it does work, for a member whose type has no ceiling in
   the way; ADR-018 §1's intersection is what the note names. A disabled box would say *you cannot
   grant this*, and that is a different and false sentence. */
.roleprivilege.capped .privilegename { color: var(--muted); }

/* Amber rather than the neutral chip colour, because this one is a caution and the two beside it
   — `needs` and `includes` — are facts about the privilege rather than about who it reaches. */
.cappednote { color: var(--warn); }

/* <b>The name and its notes on one line, the sentence under them.</b> D-100: what a privilege
   does is a sentence, and a sentence in a row of chips is a chip nobody reads. The wrapper
   exists so the checkbox stays aligned with the name rather than with the middle of a
   two-line block. */
.privilegetext { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--gap-2); min-width: 0; }
.privilegewhat { flex-basis: 100%; color: var(--muted); font-size: var(--t-small); max-width: 70ch; }

/* The Member roles page: the list is 300 pixels and keeps its place while the editor, which takes the rest,
   scrolls; the editor has room inside its edge, and its Save bar is the `.savebar` the group Settings use. */
.pickpair.rolepair { grid-template-columns: minmax(240px, 300px) minmax(0, 1fr); }
.rolepair .rolelistcol { position: sticky; top: 96px; }
.rolepair #roleEditor { padding: var(--gap-5) var(--gap-5) 0; }
.rolepair #roleEditor .editorhead { align-items: flex-start; flex-wrap: wrap; }
.rolepair #roleEditor .editorhead h3 { font-size: var(--t-head); }
.rolepair #roleEditor .editorhead .hint { margin: 2px 0 0; max-width: 60ch; }
.rolepair #roleEditor .savebar { margin: var(--gap-4) calc(-1 * var(--gap-5)) 0; }
.rolepair .rowmeta { display: block; font-size: var(--t-small); color: var(--muted); font-weight: 400; }
#roleRows button.rowname { font-weight: 600; }
@media (max-width: 900px) {
  /* One column on a phone: this rule's two-class selector outweighed the shared one-column rule (0,2,0 over 0,1,0),
     so at 390 the editor kept 50 pixels (verification round, 2026-10-19). */
  .pickpair.rolepair { grid-template-columns: minmax(0, 1fr); }
  .rolepair .rolelistcol { position: static; }
  .rolepair #roleEditor { padding: var(--gap-4) var(--gap-4) 0; }
  .rolepair #roleEditor .savebar { margin: var(--gap-4) calc(-1 * var(--gap-4)) 0; }
}
.roleprivilege code.mono, .roleprivilege .privilegename { overflow-wrap: anywhere; }

/* ---------------------------------------------------------------- the selected row

   <b>Written 2026-08-18 after a design review, and it repairs two screens.</b> `loadGroups` and
   `loadRoles` both write `class="pick on"` onto the row they have open and there was no rule for
   it — so clicking a row changed nothing visible, and the panel it opens is below the fold on a
   1366-wide window. The pair mirrors the hover rules above: a soft fill for the row, and a bar on
   the leading edge, which is the one mark that survives a colour-blind reader and a printout. */
tr.pick.on td { background: var(--accent-soft); }
tr.pick.on td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

/* ---------------------------------------------------------------- an editor's own head

   The markup on the Groups and Roles editors was written expecting a flex row with its last child
   pushed right — two sibling divs, actions and an exit — and used `class="head"`, which does not
   exist. So the buttons stacked on two left-aligned lines. Named `editorhead` rather than `head`
   because `pagehead` already owns the top of a screen and two classes one letter apart is how the
   next person picks the wrong one. */
.editorhead { display: flex; align-items: center; gap: var(--gap-3); margin-bottom: var(--gap-3); }
.editorhead > :last-child { margin-left: auto; }
.editorhead h3 { margin: 0; }

/* ---------------------------------------------------------------- a group's page

   <b>The `#groupEditor` rules that were here are gone with the panel.</b> They existed to give a
   panel's non-table children the 16px gutter that `th`/`td` carry themselves — a problem a page does
   not have, because a page's children are already inside the workspace padding.

   Two small additions, both needed by the tabs and both general:
     `.faint` for a second fact inside a cell that already carries one — *by whom* under a date, where
       `.val` is already spent on the date itself.
     `tr.after` for the line between the members who can manage a group and the members who cannot. It
       is the reference's *Group role* filter drawn as a boundary, which is what nine rows want
       instead of a filter. */

/* The second fact in a cell whose first fact is already `.val` — *by whom* under a date. `--faint`
   rather than `--muted` so the two are distinguishable, which is only defensible now that `--faint`
   can be read. */
.faint { color: var(--faint); font-size: var(--t-small); }

/* <b>A labelled band where a hairline was.</b> `tr.after` drew `border-top: 1px solid var(--rule)`
   directly against the bottom rule every `td` already carries — two near-identical hairlines stacked,
   which is invisible. And it depended on a sort that put the managers last, so it landed on the first
   row of page two and nowhere near the boundary. A row that says what it separates survives a page
   break, which a rule does not. */
tr.groupband > td {
  padding: 10px var(--gap-4) 5px; border-top: 1px solid var(--rule);
  color: var(--muted); font-size: var(--t-small); background: var(--surface-2);
}

/* Members beside services: the relation is the subject, so the two halves are read together.
   Collapses to one column where there is not room for two three-column tables. */
/* ═══════════════════ a list to choose from and a panel to work in, 2026-09-05 ═══════════════════

  <b>The 1c handoff asks two screens for the same shape and they are the same class.</b> Roles is
  *list left (click selects), privilege editor right*; Data sources is *table left, register form
  right with the test result inline*. Measured off the prototype: `minmax(0,1fr) 380px` for the
  sources and `minmax(0,1fr) minmax(360px,440px)` for the roles, gap 22 — which is `--gap-5`
  exactly, so the token was already the design's.

  <b>One class rather than two, because the difference between 380 and 360–440 is not a decision.</b>
  Writing both would be two numbers to keep in step and nothing to keep them there — D-46's subject.

  <b>`align-items: start`, or the short side stretches.</b> A grid item fills its row by default, so
  a 200-pixel form beside a twenty-row table grows a 600-pixel white box under itself.
*/
/* <b>The connection test's answer, in the form that asked it.</b> The 1c handoff: *table left,
   register form right with the test result inline (green/amber/red box)*. It was a pill and a
   sentence in a row, which reads as a status line rather than as the answer to the button above
   it — and the three tones are already in the palette, so the design's colours and this page's
   are the same colours rather than two sets that look alike.

   <b>Three tones for four outcomes, and the grouping is the honest one.</b> `Usable` is green;
   `CannotConnect` is red, because nothing about the data is known and the next move is a network
   or a password; `InsufficientPrivilege` and `UnusableGeometry` are amber, because the connection
   worked and what was found is the problem. Red for *cannot reach* and amber for *reached and it
   is wrong* is the distinction an administrator acts on. */
.testresult {
  margin-top: var(--gap-4); padding: 11px 13px; border-radius: 5px;
  border: 1px solid var(--rule); background: var(--surface-2);
  font-size: 13.5px; line-height: 1.5; color: var(--ink);
}

.testresult > b { display: block; margin-bottom: 2px; }
.testresult.ok { background: var(--ok-soft); border-color: var(--ok-line); }
.testresult.ok > b { color: var(--ok); }
.testresult.warn { background: var(--warn-soft); border-color: var(--warn-line); }
.testresult.warn > b { color: var(--warn); }
.testresult.alert { background: var(--alert-soft); border-color: var(--alert-line); }
.testresult.alert > b { color: var(--alert); }

.pickpair {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(360px, 420px);
  gap: var(--gap-5); align-items: start;
}

.pickpair > .panel { margin-bottom: 0; }

/* Same trap as `.grouppair`, and the same fix before it is measured: `1fr` cannot shrink below
   min-content, and the left side of both of these holds a table. */
@media (max-width: 1180px) { .pickpair { grid-template-columns: minmax(0, 1fr); } }

.grouppair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap-5);
             margin-top: var(--gap-4); }
/* Same trap, same fix, before it is measured rather than after: `1fr` cannot shrink below
   min-content, and this pair holds a table. */
@media (max-width: 1100px) { .grouppair { grid-template-columns: minmax(0, 1fr); } }

/* The facts, which were two paragraphs of grey hint carrying one word each. A label/value pair is
   the reference's Details block reduced to what we have: the standing and what the group confers. */
/* `minmax(0, 1fr)` and `min-width: 0`, because an address field in a value set the column's width and the web map's
   item scrolled sideways at 320 (UX sweep 2026-10-19). Baseline, so a label stays on its value's line when the value
   carries a button, as Owner's Change owner… does. */
.facts2 { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px var(--gap-4); align-items: baseline;
          font-size: var(--t-small); margin: var(--gap-3) 0; }
.facts2 dt { color: var(--muted); }
.facts2 dd { margin: 0; font-weight: 600; min-width: 0; }
.facts2 dd .val { font-weight: 400; }

/* ---------------------------------------------------------------- the tab strip

   <b>The one new component a group's page needed</b>, and the budget was one. Everything else on
   that page has an owner already: `.crumbs` and `.pagehead` for the frame, `.facts2` for Overview,
   `.setting` for Settings, `.picker` for both pickers, `pagerFor` for paging.

   <b>Not the surface strip and not the editor's left column.</b> `#surfaces` fills its current pill
   because filled means *environment*, and these four are not four environments. `#editNav` was the
   near miss: free CSS and consistent with both editors, but it spends 196px of the left edge on the
   tab that most needs width, and that left edge is exactly where Content's filter rail goes when
   maps arrive. Two nested left rails is the shape to avoid.

   An underline rather than a box, because the strip sits under a title and a boxed tab would compete
   with the panel below it for the same edge. */

.tabstrip {
  display: flex; gap: var(--gap-5); align-items: stretch;
  border-bottom: 1px solid var(--rule); margin-bottom: var(--gap-5);
}
.tabstrip a {
  padding: 8px 1px 9px; color: var(--muted); text-decoration: none;
  font-size: var(--t-body); border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabstrip a:hover { color: var(--ink); }
.tabstrip a[aria-current="page"] {
  color: var(--ink); font-weight: 600; border-bottom-color: var(--accent);
}
.tabstrip .count { color: var(--muted); font-variant-numeric: tabular-nums; font-size: var(--t-small); }

/* A link inside a sentence is underlined, so it is not told apart by colour alone (axe link-in-text-block,
   sweep 2026-10-19). */
#groupCrumb a, #groupSummary a, #groupAbout a, #groupRecent a, #groupSettings p a {
  text-decoration: underline; text-underline-offset: 2px;
}
.tabstrip a[aria-current="page"] .count { color: var(--muted); }

/* Overview's reachability sentence: a comparison of two numbers, so prose with the numbers set
   strongly — not a donut, which is the Services screen's idiom for a distribution of states.

   <b>Named `.reach`, because `.tally` is already taken and taking it broke the sentence.</b> The
   Server overview's big-number stat is `.tally`, and its rule at line 986 is `.tally b { display:
   block; font-size: 33px }`. Wearing that class turned every strong number *and the word* `group`
   into a 33-pixel block on its own line: six lines of poster where a sentence was written. Found in
   the design review of 2026-08-18, which is also the first time anybody read the sentence. */
.reach { margin: 0 0 var(--gap-5); font-size: 15.5px; max-width: 88ch; }
.reach b { font-weight: 650; }
.reach .off { color: var(--warn); }

/* A sentence does not fit a 116px right-aligned number field.

   <b>258px did not fit either, and the house rule depended on it.</b> This console puts the
   consequence in the option's own text, so a box that clips its options defeats the rule that
   justifies them: measured, half the labels — including two of the three defaults — rendered as
   `By invitation — an owner or m▾`. Fixed on both sides. The box is wider, and the labels are shorter
   with the consequence moved into the row's hint where it can wrap. */
.setting.wide { max-width: 760px; }
/* A checkbox is not a text box: the 300px meant for a field left about 150 pixels of dead space
   between the delete lock and its own Locked/Not locked label, so the row read as two unrelated
   things rather than one control and its state. Measured 2026-08-19. */
/* <b>`max-width` beside the `width`, because a fixed width in a flexible row is a clip waiting
   for a narrow container.</b> [D-216](../../../docs/architecture-debt.md) was raised when a design
   review measured this rule overflowing a 359-pixel panel by 51 px on the symbology page, hidden
   with no scrollbar. That instance is gone: ADR-053 rebuilt that screen and its *Change:* row is
   no longer a `.setting.wide`. Checked 2026-09-05 rather than assumed — the only `.setting.wide`
   rows left are the ten in `#groupSettings`, which sits in a full-width view under this rule's own
   `max-width: 760px`, so nothing overflows today.
   <b>The declaration goes in anyway and the row is closed as prevention, not repair.</b> A control
   that cannot give up width is one narrow column away from being clipped again, and the next
   person to put a wide row in a rail will not know that. `min-width: 0` is what lets it actually
   shrink inside a flex row, which `max-width` alone does not. */
.setting.wide select, .setting.wide input:not([type=checkbox]) {
  width: 300px; max-width: 100%; min-width: 0; text-align: left;
}
.setting.wide label { display: inline-flex; align-items: center; gap: var(--gap-2); }
.setting.wide .u { width: auto; flex: 0 0 auto; }
.setting.wide .q { max-width: 400px; }
/* A name or a sentence is prose, not a number: `.setting`'s monospace was set for figures with units, and a
   wide row inherited it — "Planning Group" in Consolas (UX sweep 2026-10-19). */
.setting.wide select, .setting.wide input:not([type=checkbox]) { font-family: inherit; font-size: var(--t-body); }

/* ---------------------------------------------------------------- a group's Settings tab, 2026-10-19

   Three sections with headings larger than the labels under them, one column of full-width fields, radio
   rows for each either/or, a Save bar that stays at the bottom of the window while the form scrolls, and
   deletion in its own card after the form, bordered all round in the alert hue. */
.gsform { max-width: 720px; border: 1px solid var(--rule); border-radius: var(--round-lg); background: var(--surface); }
.gsec { padding: var(--gap-5); }
.gsec + .gsec { border-top: 1px solid var(--rule); }
.gsec h3 { font-size: var(--t-head); margin: 0 0 var(--gap-1); }
.gsec > .hint { margin: 0 0 var(--gap-4); }
.gfield { display: grid; gap: 6px; margin-bottom: var(--gap-4); }
.gfield:last-child { margin-bottom: 0; }
.gfield label { font-weight: 600; }
.gfield input, .gfield textarea { width: 100%; font-family: inherit; font-size: var(--t-body); }
.gfield .hint { margin: 0; }
.gsec fieldset.pickset { margin-bottom: var(--gap-5); }
.gsec fieldset.pickset:last-child { margin-bottom: 0; }
.gsec .pickrow { padding: var(--gap-3) var(--gap-4); }
.gsec .pickrow:has(input:disabled) { cursor: not-allowed; color: var(--muted); }
.gfixed { margin: 0; padding: var(--gap-3) var(--gap-4); border: 1px solid var(--rule); border-radius: var(--round);
          background: var(--surface-2); }
.gfixed .hint { display: block; margin: 2px 0 0; }
.savebar { position: sticky; bottom: 0; z-index: 2; display: flex; flex-wrap: wrap; align-items: center;
           justify-content: flex-end; gap: var(--gap-2); padding: var(--gap-3) var(--gap-5);
           border-top: 1px solid var(--rule); background: var(--surface);
           border-radius: 0 0 var(--round-lg) var(--round-lg); }
.savebar > .val { margin-right: auto; }
/* Pinned only while there is something to save; with nothing changed it sits where it is and takes no screen.
   On a phone it is always one line (verification round, 2026-10-19). */
.savebar:not(.dirty) { position: static; }
@media (max-width: 600px) {
  .savebar { flex-wrap: nowrap; padding: var(--gap-2) var(--gap-3); }
  .savebar > .val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .savebar > button { flex: none; }
}
.dangercard { max-width: 720px; margin-top: var(--gap-5); border: 1px solid var(--alert-line);
              border-radius: var(--round-lg); background: var(--surface); }
.dangercard h3 { font-size: var(--t-head); margin: 0; padding: var(--gap-4) var(--gap-5) 0; color: var(--alert); }
.dangerrow { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
             gap: var(--gap-3) var(--gap-5); padding: var(--gap-4) var(--gap-5); }
.dangerrow + .dangerrow { border-top: 1px solid var(--rule); }
.dangerrow > div { flex: 1 1 280px; min-width: 0; }
.dangerrow .hint { margin: 2px 0 0; }
.dangerrow button.danger { border-color: var(--alert-line); }
.dangerswitch { display: inline-flex; align-items: center; gap: var(--gap-2); font-weight: 600; }
.dangerswitch input { width: 18px; height: 18px; accent-color: var(--accent); }

/* ---------------------------------------------------------------- the add page

   <b>The rail slot the group Content tab reserved, spent on the one thing we have data for.</b>
   `#serverGrid`'s shape, and the same media query: below 1100px the rail stacks above the table rather
   than squeezing the rows that carry a 104px picture. */

.addgrid { display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: var(--gap-5); }
@media (max-width: 1100px) { .addgrid { grid-template-columns: minmax(0, 1fr); } }

/* The Publish screen's contents tree — ADR-057, and the shape is ArcGIS Pro's contents pane
   because that is the reference the owner named for this screen.

   <b>A tree rather than a list, since 2026-09-06.</b> It was a flat row per layer, and the
   design study it was built from had a root, disclosure triangles, a tick and a symbol under
   each layer. The owner put the two side by side and asked whether they were the same screen;
   they were not. */

.pubroot { display: flex; align-items: center; gap: 8px; padding: 8px 10px;
  border-bottom: 1px solid var(--rule); font-weight: 600; }
.pubroot .pubglyph { color: var(--link); }

/* <b>The map's reference, and it is a label rather than a second name.</b> The per-layer badge
   is styled as `.pubrow .pubsr`, and `.pubroot` is a different ancestor — so the root's badge
   matched no rule at all and inherited the row's own 600 weight, arriving at the same visual
   weight as the word *Map* beside it. A found review, 2026-09-07: the badge exists to be read at
   a glance as secondary, and unstyled it read as a second heading. */
.pubroot .pubsr { margin-left: auto; flex: 0 0 auto;
  font: var(--t-tiny)/1 var(--mono); font-weight: 400; color: var(--faint); }
.pubmapname { flex: 1; }

.pubmenu { border: 0; background: none; cursor: pointer; color: var(--faint);
  padding: 2px 6px; border-radius: var(--round); font-size: 15px; line-height: 1; }
.pubmenu:hover { background: var(--surface-2); color: var(--ink); }

/* The triangle is a button, so it is reachable without a mouse and announces its own state. */
.pubtwist { border: 0; background: none; cursor: pointer; color: var(--faint);
  padding: 0; width: 14px; font-size: 10px; line-height: 1; flex: none; text-align: left; }
.pubtwist:hover { color: var(--ink); }

.pubtick { margin: 0; flex: none; }
.pubglyph { color: var(--faint); flex: none; }

/* <b>The reprojection mark, and only where there is one.</b> An arrow beside every layer is
   decoration; beside the three whose tables are stored in something else it is the answer to
   *what is this service doing to my data*. */
.pubwarp { color: var(--link); font-size: 13px; flex: none; cursor: help; }

/* The symbol, under the layer it belongs to — the row the disclosure triangle reveals. */
.pubsym { display: flex; align-items: center; gap: 9px; width: 100%;
  border: 0; background: none; cursor: pointer; text-align: left;
  padding: 3px 10px 5px 44px; color: var(--muted); font-size: var(--t-small); }
.pubsym:hover { background: var(--surface-2); color: var(--ink); }
.pubnode.grouped .pubsym { padding-left: 62px; }

.pubswatch { flex: none; width: 22px; height: 14px; }
.pubswatch.fill { background: var(--sw-fill); border: 1.5px solid var(--sw-line);
  border-radius: 2px; }
.pubswatch.line { height: 0; border-top: 2.5px solid var(--sw-line); }
.pubswatch.dot { width: 12px; height: 12px; border-radius: 50%;
  background: var(--sw-fill); border: 1.5px solid var(--sw-line); }

/* <b>The picture, and it is the server's.</b> `POST /admin/publish/preview` draws the
   composition out of the databases with the renderer the served faces use, so this is the
   service before the service exists. The chequerboard is what says the ground is transparent
   rather than white — a preview on white would promise a background the service does not
   serve. */
/* <b>The chequerboard went with the empty state it explained.</b> There is a map under the
   drawing now, so transparency is not a thing to signal — it is how the composition gets to sit
   on a ground. */
.pubcanvas { position: relative; min-height: 300px; flex: 1 1 auto; overflow: hidden;
  padding: 0; background: var(--surface-2); }

#pubMap { position: absolute; inset: 0; }
#pubMap .ol-viewport { border-radius: 0; }

/* <b>Over the map and out of the way of it.</b> The picture must not swallow the drag, the
   scroll or the pointer — panning the ground under it is the whole reason the map is here. */
.pubcanvas > img { position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 1; }

.pubmapsays { position: absolute; left: 10px; bottom: 10px; z-index: 2;
  max-width: 60%; padding: 6px 10px; border-radius: var(--round);
  background: rgba(255, 255, 255, .92); border: 1px solid var(--rule);
  font-size: var(--t-small); color: var(--muted); box-shadow: var(--lift); }

/* Where a table dropped on the map will land. */
.pubcanvas.dropping { outline: 2px dashed var(--accent); outline-offset: -6px; }

/* <b>The layer's number, where the layer is.</b> `FeatureServer/2` is how a client addresses
   it, and a group and its children interleave — so counting rows is not reading the index. It
   was a list under the preview repeating every name until 2026-09-06, which is one fact in two
   places and the owner said so. Muted and monospaced: it is an address, not a heading, and it
   must not compete with the name beside it. */
.pubindex { flex: none; width: 15px; text-align: right;
  font: var(--t-tiny)/1 var(--mono); color: var(--faint); }

/* The reference is set on the map's properties, so its box is a dialog field like any other —
   monospaced, because what goes in it is a code or a definition and both are read character by
   character. It was a rule for a toolbar control until 2026-09-07; ADR-057 §5n moved it. */
#mapprops #pubSrid { font-family: var(--mono); }

/* What the server says about the code that was typed: its name, or that it does not know it.
   The width cap was for the toolbar, where it sat between two buttons; in the dialog the
   sentence has the width of the dialog and capping it would wrap a name for no reason. */
.pbsridsays { font-size: var(--t-small); color: var(--muted); }
.pbsridsays.bad { color: var(--alert); }

/* A value the dialog reports rather than asks for. */
/* <b>And a refusal reads as one here too.</b> `.pbsridsays.bad` colours the same sentence on the
   map's properties; without this rule the Publish dialog showed *this server cannot project to…*
   in unalarmed black, at the one moment it matters most. The same fact in two places, styled in
   one — which is the shape of the defect this pair of controls was built to end. */
.pbreadonly.bad { color: var(--alert); }

.pbreadonly { display: block; padding: 7px 0 0; font: 500 var(--t-body)/1 var(--mono);
  color: var(--ink); }

/* The contents tree's context menu. */
#pubmenu {
  position: fixed; z-index: 60; min-width: 178px; padding: 4px;
  background: var(--surface); border: 1px solid var(--rule-strong);
  border-radius: var(--round); box-shadow: var(--lift-2);
}
#pubmenu button {
  display: block; width: 100%; text-align: left; border: 0; background: none;
  padding: 7px 10px; border-radius: 3px; cursor: pointer;
  font-size: var(--t-small); color: var(--ink);
}
/* Hover and focus are the same state here, because the menu takes focus now: the arrow keys
   move through it and a highlight nobody can see is the same as no highlight. */
#pubmenu button:hover,
#pubmenu button:focus-visible { background: var(--surface-2); outline: none; }
#pubmenu button:disabled { color: var(--faint); cursor: default; background: none; }
#pubmenu hr { border: 0; border-top: 1px solid var(--rule); margin: 4px 2px; }

/* The Publish dialog's *What it can do* block — ADR-057 §5g.

   A fieldset because it is one question with several answers, and a legend because a screen
   reader announcing "checkbox, Create" without it has told the reader nothing about what is
   being created. `.tick` next door is a table-cell width and is deliberately not reused: 34px
   on a label would crush the sentence beside the box. */

.pbcaps { border: 1px solid var(--rule); border-radius: var(--round); padding: 10px 12px 4px;
  margin: 4px 0 0; }
.pbcaps legend { padding: 0 6px; font: 600 var(--t-label)/1 var(--sans); color: var(--faint);
  text-transform: uppercase; letter-spacing: .04em; }
.pbcaps .row { gap: var(--gap-3); }
.pbcaps .hint { margin: 6px 0 8px; }

.pbtick { display: flex; align-items: baseline; gap: 8px; flex: 0 1 auto; cursor: pointer; }
.pbtick input { margin: 0; flex: 0 0 auto; }
.pbtick input:disabled { cursor: default; }

/* A disabled box that is ticked on purpose reads as *settled*, not as broken — the same
   problem `tr.already` below solves in a table. */
.pbtick input:disabled + span { color: var(--muted); cursor: default; }

.tick { width: 34px; }
.thumbcell { width: 120px; padding-right: 0; }

/* Already in the group: ticked, disabled, and legible as *not an option* rather than as broken. */
tr.already { color: var(--muted); }
tr.already .name { font-weight: 500; }

/* The select-all, whose label carries its own count — the whole point of not being a bare tick. */
.pickall { display: flex; align-items: center; gap: var(--gap-2); font-size: var(--t-small);
           color: var(--muted); white-space: nowrap; }

/*
  <b>Stuck to the bottom, because the count of what you picked was below the fold.</b> Measured
  2026-08-19 on the screen the owner asked for by name: with the picker open, `Add N items` sat at
  y=1481 in a 900-pixel viewport and y=1663 in a 720-pixel one. Everything above the fold said how
  many items *exist*; nothing said how many were *chosen*, and the select-all's only feedback was a
  native indeterminate dash. A confirmation you have to scroll to find is a confirmation that does not
  confirm.
*/
.addfoot { position: sticky; bottom: 0; z-index: 2;
           margin-top: var(--gap-5); gap: var(--gap-3); align-items: baseline; flex-wrap: wrap;
           padding: var(--gap-3) 0; background: var(--surface);
           border-top: 1px solid var(--rule); }
.addfoot .hint { flex: 1 1 340px; min-width: 240px; }

/* ---------------------------------------------------------------- the inline picker

   <b>Its own rules, because it was wearing `.setting`.</b> That class is a 620px flex row built for
   a number and a unit: a 116px right-aligned monospace field. A listbox of `folder/service` paths
   inherited the width and the right-alignment, where the meaning is on the left and so is the
   truncation, and the checkbox inherited 116px as well. This is D-46's failure in reverse — not a
   shape rebuilt because the old one was hard to find, but an old shape borrowed for a job it was
   not built for. */
.picker { border: 1px solid var(--rule); border-radius: var(--round-lg);
          padding: var(--gap-3) var(--gap-4); background: var(--surface-2);
          margin: var(--gap-3) 0; max-width: 620px; }
.picker label { display: block; font-size: var(--t-small); color: var(--muted);
                margin-bottom: 4px; }
.picker input[type=search] { width: 100%; margin-bottom: var(--gap-2); }
.picker select { width: 100%; text-align: left; font-family: var(--mono);
                 font-size: var(--t-small); }
.picker .row { display: flex; align-items: center; gap: var(--gap-3);
               margin-top: var(--gap-3); }
.picker .row label { display: flex; align-items: center; gap: 6px; margin: 0; }
.picker .row input[type=checkbox] { width: auto; }
.picker .row > :last-child { margin-left: auto; }
/* ADR-204 §5e item 16: somebody from another organization, typed. A full-width field, because a qualified name is
   an organization key and an e-mail address end to end; the browser's default of about 20 characters cut it in two. */
.picker .pickother { margin-top: var(--gap-3); }
.picker .pickother input { width: 100%; font-family: var(--mono); font-size: var(--t-small); }
.picker .pickother .hint { margin: var(--gap-1) 0 0; }
.picker .hint code, .memberfrom { overflow-wrap: anywhere; }
.picker #groupPickSays { margin: var(--gap-2) 0 0; }
.memberfrom { font-weight: 400; }

/*
  <b>The Logs screen. ADR-045.</b> Three rules, and no new concepts: the rows are the table
  every other screen uses, the states are this file's own pills, and the only things missing
  were a column that must not wrap and the detail row behind a click.
*/
.nowrap { white-space: nowrap; }

/* A log row is clickable, and it has to look it — a row that reveals something on click and
   looks identical to one that does not is a control nobody finds. */
tr.logrow { cursor: pointer; }
tr.logrow:hover td { background: var(--surface-2); }

/* The detail is monospace and scrolls in its own box rather than widening the table, which
   is the same reasoning `widetable` carries: a long value must not push the page sideways. */
tr.logdetail td { background: var(--surface-2); padding: 0; }
tr.logdetail pre { margin: 0; padding: var(--gap-3) var(--gap-4);
                   font: 12px/1.5 var(--mono); color: var(--muted);
                   max-height: 22rem; overflow: auto; }

/*
  <b>The Logs screen's toolbars wrap, and the reason is that there are six controls in one.</b>
  A `.toolbar` is `display: flex` with no `flex-wrap`, which is right for the two or three
  controls every other screen puts in one — at 1024px this screen's filter row pushed *Only
  failures* off the edge and dragged the whole page, sidebar included, sideways. Scoped to this
  screen rather than changed globally: the other toolbars were measured and fit.
*/
#view-logs .toolbar { flex-wrap: wrap; row-gap: var(--gap-2); }
#view-logs .toolbar input[type=search],
#view-logs .toolbar input[type=text] { flex: 1 1 12rem; }

/* A label beside its control, so the pair wraps as one thing rather than splitting. */
#view-logs #logOwn { display: inline-flex; align-items: center; gap: var(--gap-2); }
#view-logs #logOwn label { color: var(--muted); font-size: var(--t-small); }
#view-logs .toolbar label { white-space: nowrap; }

/*
  <b>The table scrolls in its box below the width its columns need.</b> `widetable` already
  does this; what it lacked was a minimum for these five columns, so the cells squeezed until
  Resource clipped instead of the box scrolling.
*/
#view-logs .widetable table { min-width: 60rem; }

/*
  <b>Which screen a cold load paints, before `console.js` has run.</b> `session.js` — loaded
  from the head without `defer`, in its own file because the console's CSP allows no inline
  script — sets `data-session` from `sessionStorage`, and these two rules are what make that
  decision visible on the first paint instead of after 454 KB of JavaScript.

  <b>Measured with `console.js` delayed 800 ms and a valid token in storage:</b> the sign-in
  panel was never visible, in any of about 1,380 samples per run across three runs. Before
  this, the same test kept it on screen for the whole delay.

  <b>`!important` on exactly these two, and it is load-bearing rather than lazy.</b>
  `console.js` sets `style.display` inline on both elements once it knows the real answer, and
  an inline style beats a class — so without this the first paint would ignore the attribute.
  These rules are the *initial* state only: `start()` replaces the attribute the moment `whoami`
  has answered. It has to change the attribute, not the inline style — an inline `none` does not
  beat `!important`, which is how the issued-password form came to have the Services screen
  painted under it. A member who must replace their password gets `data-session="mustchange"`.
*/
html[data-session="held"] #signin { display: none !important; }
html[data-session="held"] #app { display: block !important; }

/*
  <b>The symbology editor: the picture beside the controls.</b> Owner request 2026-09-03 — the
  page had a JSON box, a derived document and swatches, which is an editor for somebody who
  already knows MapLibre. What was missing is controls that name what they do and a picture of
  the result, and the picture is the half that decides: a swatch says what colour, not what the
  map looks like at the size anybody sees it.

  <b>Rebuilt to the owner's handoff, direction 1c, 2026-09-04.</b> The 2026-09-03 answer put the
  picture beside a single column of controls and left the rest of the page — the document, the
  ArcGIS projection, the losses — stacked underneath, so the screen was still a scroll. The
  complaint it was answering was *çok karmaşık*, and a shorter scroll is not an answer to that.

  <b>Three columns, and each one is a question.</b> *What kind of drawing is this* on the left,
  *what does it look like* in the middle, *what is in it* on the right. Nothing was removed: the
  document, the losses and the derived `drawingInfo` are tabs of the third column rather than
  three more sections below the fold, and the preview stops being a 336-pixel thumbnail beside a
  form and becomes the thing the page is mostly made of.
*/

/* <b>The editor takes the window, so the shell's page padding is not its padding.</b> Every
   other screen is a document in a 44-pixel margin; this one is an instrument panel, and a
   column of controls that stops 44 pixels short of the edge reads as a panel that failed to
   load. `#app` keeps its padding for every other view. */
#app.symfull { padding: 0; max-width: none; }
#app.symfull #editHead { display: none; }
#app.symfull #edit {
  display: block; min-height: 0; border: 0; border-radius: 0; box-shadow: none;
  background: transparent;
}
#app.symfull #editNav { display: none; }
#app.symfull #editPages { padding: 0; }

/* <b>Full height minus the top bar, so the three columns end where the window does.</b> The
   preview is the middle column and it fills what is left; without a height on the page it would
   be as tall as whichever column had the most in it.
   <b>The 1,180 is the three columns' floor, not the screen's.</b> Below it the columns stack
   rather than being cut off by `#edit`'s rounded corners — see *below the editor's floor*,
   D-221, further down, which lifts this minimum. */
#app.symfull #page-symbology.on {
  display: flex; flex-direction: column;
  height: calc(100vh - var(--header-h, 58px));
  min-width: 1180px;
}

/* ------------------------------------------------------------------ the title strip
   <b>One line that says where you are, what you are looking at and what you can do with
   it.</b> The three used to be three rows — a breadcrumb, a tab strip, a row of buttons at the
   bottom of the form — and Store was the one below the fold. */

.symstrip {
  display: flex; align-items: center; gap: var(--gap-2);
  padding: 0 var(--gap-5); min-height: 44px; flex: none;
  background: var(--surface); border-bottom: 1px solid var(--rule);
  font-size: var(--t-small); color: var(--muted);
  position: sticky; top: 0; z-index: 5;
}

.symstrip .crumbs { white-space: nowrap; }
.symstrip .crumbs a { color: var(--muted); }
.symstrip .crumbs a:hover { color: var(--link); }
.symstrip .crumbs b { color: var(--ink); font-weight: 600; }

.symstrip .symdo { margin-left: auto; display: flex; align-items: center; gap: var(--gap-2); }

/* <b>The state line is the caption the preview used to carry, and it belongs beside Store.</b>
   *Edited — this is what Store would keep* is a sentence about the button next to it. */
.symstate { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.symstate.symstate-edited { color: var(--warn); font-weight: 600; }

/*
  <b>A segmented control, and this file had three hand-rolled ones before it had this.</b> The
  layer switcher, the item tabs and the inspector's own tabs are the same shape: a row of links
  in a hairline box where the current one is a raised chip. Written once (D-46).
*/
.segmented {
  display: inline-flex; gap: 2px; padding: 3px; flex: none;
  border: 1px solid var(--rule); border-radius: 6px; background: var(--paper);
}
.segmented a {
  padding: 3px 10px; border-radius: 3px; color: var(--muted);
  font-size: 12.5px; white-space: nowrap; text-decoration: none;
}
.segmented a:hover { color: var(--ink); text-decoration: none; }
.segmented a[aria-current] {
  background: var(--surface); color: var(--ink); font-weight: 600; box-shadow: var(--lift);
}

/* The item page's own tabs, which are a level up from the layer switcher beside them: they move
   between subjects, so they are the taller of the two. */
.segmented.tabs { background: var(--surface); margin-left: auto; }
.segmented.tabs a { padding: 5px 12px; font-size: var(--t-small); }

/* ------------------------------------------------------------------------ the banner
   The server's own refusal, said where the thing it refused was typed. Nothing else on the page
   changes: a document that was not stored is still the document on the screen. */
.symbanner {
  display: flex; gap: var(--gap-3); align-items: flex-start; flex: none;
  padding: 10px var(--gap-5);
  background: var(--alert-soft); border-bottom: 1px solid var(--alert-line);
  color: var(--alert); font-size: var(--t-small); line-height: 1.45;
}
.symbanner .mono { font-family: var(--mono); }
.symbanner button { margin-left: auto; flex: none; }

/* --------------------------------------------------------------------- the empty state
   <b>A generated appearance is an answer, so it is stated as one.</b> §5b: no document is
   stored, the colour is deterministic, and both faces report version 0. That used to be a
   sentence above a form already full of controls for a document nobody had written. */
.symempty { flex: 1; display: grid; place-items: center; padding: 40px; }
.symempty > div {
  max-width: 560px; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: var(--gap-3);
}
.symempty b { font-size: 19px; letter-spacing: -.01em; }
.symempty p { margin: 0; color: var(--muted); }
.symempty .row { margin: 0; }
.symempty .sw {
  width: 120px; height: 80px; border-radius: 6px; display: grid; place-items: center;
  box-shadow: 0 0 0 1px var(--rule);
  background: repeating-linear-gradient(135deg, #eef2f6 0 6px, #e6ebf1 6px 12px);
}

/* ------------------------------------------------------------------- the three columns */

.symcols {
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: 264px minmax(0, 1fr) 336px;
}

/* <b>Renderer.</b> A column of sections, each one a question with a heading over it. */
.symrail {
  background: var(--surface); border-right: 1px solid var(--rule);
  padding: var(--gap-4) var(--gap-3); overflow: auto;
  display: flex; flex-direction: column; gap: var(--gap-4);
}

.symrail h5, .syminsp h5 {
  margin: 0 0 var(--gap-2);
  font: 600 11.5px/1.3 var(--sans); letter-spacing: .06em; text-transform: uppercase;
  color: var(--faint);
}

/* The rail is 264px wide with 24px of padding, so a label at one edge and a control at the
   other is not an arrangement it can afford. Label over control. */
.symrail .setting { display: block; padding: 0 0 var(--gap-2); max-width: none; }
.symrail .setting .q { display: block; margin-bottom: 3px; font-size: 12.5px; color: var(--muted); }
.symrail .setting select,
.symrail .setting input:not([type=checkbox]):not([type=color]) { width: 100%; }
.symrail .hint { margin: 2px 0 var(--gap-2); font-size: 12px; }

/* ------------------------------------------------------- the renderer, as three cards
   <b>A `<select>` was three answers behind a click.</b> The three are the biggest decision on
   the page and they are visually different from each other — one colour, a colour per value, a
   ramp — so they are shown rather than named. Radios, so a keyboard moves between them with the
   arrow keys and a screen reader is told it is one choice of three. */

.symkinds { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--gap-3); }

.symkind {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px; border: 1px solid var(--rule); border-radius: 6px;
  background: var(--surface); color: var(--muted); font-size: var(--t-small);
  cursor: pointer;
}
.symkind:hover { border-color: var(--rule-strong); }
/* <b>The card is the radio's containing block — `position: relative`, added 2026-09-11.</b>
   Without it the hidden radio was positioned against a box outside every scroller on the page,
   so it did not move when the rail scrolled: focusing a card in a scrolled rail left the card, and
   its focus ring, out of sight. Measured at 1,300 below the floor (D-221): card at 300 pixels,
   rail box from 549. It was latent in the wide window only because the rail there overflows by
   fourteen pixels. */
.symkind { position: relative; }
.symkind input { position: absolute; opacity: 0; width: 0; height: 0; }
.symkind:has(input:checked) {
  border: 2px solid var(--accent); padding: 8px 10px;
  background: var(--accent-soft); color: var(--ink); font-weight: 600;
}
.symkind:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

.symkind .ramp { display: flex; gap: 2px; flex: none; }
.symkind .ramp i { width: 7px; height: 5px; border-radius: 2px; display: block; }

/* ------------------------------------------------------------------------ the picture
   <b>The preview is the column, not a thumbnail in it.</b> It is what the reader came to look
   at; the hatched ground behind it is the same one the empty thumbnails on every list use, so a
   picture that has not arrived reads as *not yet* rather than as *nothing*. */

.sympreview {
  position: relative; overflow: hidden; min-width: 0;
  background: repeating-linear-gradient(135deg, #eef2f6 0 6px, #e6ebf1 6px 12px);
}

.sympreview img,
.sympreview .thumb.empty {
  display: block; position: absolute; inset: 0;
  width: 100%; height: 100%; border-radius: 0; background: none; box-shadow: none;
  object-fit: contain;
}

.sympreview .thumb.empty { display: grid; place-items: center; }

/* The overlays. All four are cards on the picture rather than chrome around it, because the
   picture is what has to keep its size when the window changes. */
.symcap {
  position: absolute; left: 14px; top: 12px; max-width: calc(100% - 28px);
  padding: 5px 9px; border-radius: 4px; background: rgba(255, 255, 255, .9);
  color: var(--muted); font: 11.5px/1.4 var(--mono);
  box-shadow: 0 0 0 1px var(--rule);
}

.symground { position: absolute; right: 14px; top: 12px; }

.symlegend {
  position: absolute; left: 14px; bottom: 14px; max-width: calc(100% - 28px);
  padding: 8px 11px; border-radius: var(--round); background: rgba(255, 255, 255, .94);
  font-size: 12px; display: flex; flex-direction: column; gap: 4px;
  box-shadow: 0 0 0 1px var(--rule);
  max-height: 40%; overflow: auto;
}
.symlegend span { display: flex; align-items: center; gap: var(--gap-2); }
.symlegend em { font-style: normal; color: var(--faint); }

/* ---------------------------------------------------------------------- the inspector
   Three tabs over one pane, and the third carries a count when the conversion lost something.
   The losses were a block under the editor that a reader reached by scrolling past everything
   else; a badge on a tab is the same fact where it can be seen without looking for it. */

.syminsp {
  background: var(--surface); border-left: 1px solid var(--rule);
  display: flex; flex-direction: column; overflow: hidden; min-width: 0;
}

.insptabs { display: flex; border-bottom: 1px solid var(--rule); flex: none; }
.insptabs a {
  flex: 1; text-align: center; padding: 11px 0; color: var(--muted);
  font-size: var(--t-small); text-decoration: none;
  display: flex; justify-content: center; align-items: center; gap: 6px;
}
.insptabs a:hover { color: var(--ink); text-decoration: none; }
.insptabs a[aria-current] {
  color: var(--link); font-weight: 600; box-shadow: inset 0 -2px 0 var(--accent);
}
.insptabs .badge {
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px;
  background: var(--warn); color: #fff;
  font: 700 10.5px/16px var(--sans); text-align: center;
}

.insppane { flex: 1; min-height: 0; overflow: auto; display: flex; flex-direction: column; }
.insppane[hidden] { display: none; }

.insppane .pad { padding: var(--gap-3) var(--gap-3) 0; }
.insppane .setting { display: flex; padding: 5px 0; max-width: none; }
.insppane .hint { margin: 2px 0 var(--gap-2); font-size: 12px; }

/* <b>The symbol stack is a footer under the list, not a page that replaces it.</b> 2026-09-04's
   answer to *a panel titled after a row nobody can see* was to show one or the other; the
   handoff's is to put them in one 336-pixel column where the selected row and its symbol are
   within a screenful of each other, and to scroll the selection into view whenever it moves.
   Both fix the same fault; this one does not cost a click to see what a class is made of. */
.symstack {
  margin-top: auto; padding: var(--gap-3);
  border-top: 1px solid var(--rule); background: var(--surface-2);
  font-size: var(--t-small);
}
.symstack .symstackhead {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: var(--gap-2);
}
.symstack .symstackhead b { font-weight: 600; }
.symstack .symstackhead span { font-size: 12px; color: var(--muted); }

/* The document, as a pane rather than a `<details>`. It is the canonical thing Store sends, and
   a disclosure triangle over it said the opposite. */
.insppane .docpane { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.insppane .docpane textarea {
  flex: 1; min-height: 0; margin: 0; border: 0; border-radius: 0; resize: none;
  padding: var(--gap-3); font: 12px/1.6 var(--mono); background: var(--surface-2);
}
.insppane .docpane textarea:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
.insppane .docfoot, .insppane .dochead {
  flex: none; padding: 10px var(--gap-3); color: var(--muted);
  font-size: 12.5px; line-height: 1.45;
  display: flex; gap: var(--gap-2); align-items: center;
}
.insppane .dochead { border-bottom: 1px solid var(--rule); }
.insppane .docfoot { border-top: 1px solid var(--rule); }
.insppane .dochead .tag {
  padding: 2px 8px; border-radius: 10px; background: var(--surface-2);
  font: 11.5px var(--mono); color: #33404f;
}

#symDerived { margin: 0 var(--gap-3) var(--gap-3); flex: 1; min-height: 120px; }

/*
  <b>One row per class, and the colour last.</b> The value is what a reader scans for, the label
  is what they write, and the colour is what they adjust — so they sit in that order, and the
  remove button is a ghost at the end where it cannot be hit on the way to anything else.
*/
.setting.symclass { display: flex; align-items: center; gap: var(--gap-2); flex-wrap: wrap; }
.setting.symclass .q { flex: 0 0 auto; }
.setting.symclass .symvalue { flex: 1 1 120px; min-width: 90px; }
.setting.symclass .symlabel { flex: 1 1 120px; min-width: 90px; }

.setting input[type="color"] {
  width: 44px; height: 30px; padding: 2px;
  border: 1px solid var(--rule); border-radius: var(--round);
  background: var(--surface); cursor: pointer;
}

#symAdvanced { margin: 12px 0 0; }
#symAdvanced summary { cursor: pointer; font: var(--t-small)/1.4 var(--sans); color: var(--muted); }
#symAdvanced[open] summary { margin-bottom: 8px; }

/* --------------------------------------------------------- the symbol layer stack
   ADR-052 §3.7. A symbol is a stack, so the editor is a list of rows rather than one
   outline shared by every class. The selected class is marked because the panel below
   belongs to it, and a panel whose subject is not visible is a panel people misread. */

.setting.symclass{cursor:pointer}

.setting.symclass.symchosen{
  background:var(--accent-soft);
  border-radius:4px;
  box-shadow:inset 2px 0 0 var(--accent);
  padding-left:8px;
}

#symStackNote{margin:2px 0 8px}

.setting.symlayer{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  border:1px solid var(--rule);border-radius:4px;
  padding:7px 9px;margin-bottom:6px;
}

.setting.symlayer .symlayerkind{
  min-width:64px;font-weight:600;
}

.setting.symlayer input[type="number"]{width:74px}

/* <b>A resting border.</b> These were ghost buttons that appeared on hover, and reordering is
   the whole idea of a stack — its controls cannot be the ones nobody can see. */
.setting.symlayer .tiny{
  border:1px solid var(--rule);
  background:var(--surface);
  min-width:30px;
}
/* <b>No `margin-left: auto`, because the row wraps.</b> Pushing the reorder and remove buttons
   to the far right aligned them down the stack, which is worth having on a row that fits — and
   this one does not always: measured at 1440, a stroke or marker row breaks before its buttons,
   and `auto` then held them at the right edge of an otherwise empty line, three-quarters of it
   blank. A cluster alone at the end of a blank line reads as a rendering fault rather than as
   the same three buttons that were there before. They follow the controls now, so a second line
   begins where the eye is already going. */
.setting.symlayer .symup{margin-left:var(--gap-2)}

#symStackActions{gap:6px}

/* ------------------------------------------------------------- the symbol library
   ADR-052 §3.8. A grid of shipped symbols, each drawn from the same stack the editor
   below edits. The swatch is an icon; the picture beside the form is the renderer's. */

#symGallery{
  display:grid;gap:8px;
  grid-template-columns:repeat(auto-fill,minmax(84px,1fr));
  margin-bottom:14px;
}

.symcard{
  display:flex;flex-direction:column;align-items:center;gap:4px;
  padding:7px 5px 6px;cursor:pointer;
  background:var(--surface);
  border:1px solid var(--rule);border-radius:5px;
  font:inherit;font-size:11px;line-height:1.25;color:inherit;text-align:center;
}

.symcard:hover{border-color:var(--accent)}
.symcard:focus-visible{outline:2px solid var(--accent);outline-offset:1px}

.symcard svg{width:100%;height:38px;display:block}

/* <b>Two lines rather than an ellipsis.</b> *Road with ca…* and *Dashed boun…* are the two
   entries somebody is looking for, and a name clipped before its distinguishing word is a name
   that does not identify anything. */
.symcard span{
  display:block;max-width:100%;
  line-height:1.2;opacity:.85;
  overflow-wrap:anywhere;
}

#symGalleryNote{margin:2px 0 8px}

/* -------------------------------------------------------- varying with a number
   ADR-052 §3.6. Two stops, side by side, with the colour or the number showing
   according to what is being varied — the other is hidden rather than disabled,
   because a greyed control still reads as something that ought to work. */

.setting.symvarystop{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.setting.symvarystop input[type="number"]{width:96px}
#symVaryNote{margin:6px 0 0}

/* The Symbology link beside the Visualization layer picker. It sits on the toolbar
   baseline rather than the label's, so it lines up with the picker and not its caption. */

#visSymbology{
  align-self:flex-end;margin-bottom:6px;
  text-decoration:none;white-space:nowrap;
}

/* ------------------------------------------------------- the service's Symbology tab
   One row per layer: what the renderer draws, what it is drawn with, and the way in. */

.symrow{
  display:flex;align-items:center;gap:14px;
  padding:10px 4px;border-bottom:1px solid var(--rule);
}
.symrow:last-child{border-bottom:0}

.symrow .thumb{width:84px;height:56px;object-fit:cover;border-radius:4px;flex:0 0 auto}
.symrowtext{flex:1;min-width:0}
.symrowname{font-weight:600}
/* <b>No opacity.</b> `--muted` is 6.18:1 on `--surface` and this row's own three-quarters
   dropped it to 3.53:1 — measured, and AA asks 4.5 for text this size. The token is already
   tuned; dimming a tuned token is undoing the tuning. */
.symrow .rowmeta{color:var(--muted);font-size:12.5px;margin-top:2px}
.symrow .primarylink{flex:0 0 auto}

/* -------------------------------------------------- the symbology form's own spacing
   `.setting .q { flex: 1 }` suits a page of numeric fields, where the unit sits at the far
   right of a fixed row. Here it puts every label at one edge and every control at the other
   with a hand's width of nothing between, so no label is beside the thing it names. */

#page-symbology .setting .q{ flex:0 1 auto; min-width:104px; }
#page-symbology .setting{ gap:var(--gap-2); }

/* A class row is a control, so it looks like one when it has focus. */
.setting.symclass{ border-radius:var(--round); }

/* <b>The row is no longer focusable, so the ring belongs to whatever inside it has focus.</b>
   `:focus-within` keeps the row itself marked while a keyboard user is in one of its boxes,
   which is what says "this is the class you are editing" now that selection follows focus. */
.setting.symclass:focus-within{ outline:2px solid var(--accent); outline-offset:1px; }

#styleState{margin:0 0 10px}
#styleAdvanced summary{cursor:pointer;margin-top:10px}
#styleAdvanced textarea{margin-top:8px}

/* -------------------------------------------- the classify row
   <b>The button does not wrap.</b> "Read the values" fits on one line and "Read the data" broke
   across three in the same fixed-width button, so switching the Draw family visibly changed the
   row's height. Measured by a design review 2026-09-04. */
#symClassifyRow .tiny{ white-space:nowrap; }
#symClassCount{ width:5.5em; }
#symClassifySays{ margin:2px 0 10px; }

/* ------------------------------------------- the class list, when it is long
   <b>Ten rows, and the height is set in script rather than here.</b> A classification may hold
   256 classes; the owner looked at that list and then at twenty and said twenty was still too
   long. Ten is their number. It cannot be written as a length in this file because a row's
   height depends on the font, the theme and the browser's zoom — so `drawSymbologyClasses`
   measures one row and sets the box to ten of them, and the list is ten rows whatever any of
   those are.

   <b>No box at all when it fits.</b> A scroller around eight rows is a frame around nothing. */
#symClasses{
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* <b>This was moved from `--rule` to `--rule-strong` on 2026-09-04 and the comment claimed the
   contrast problem was fixed. It was not.</b> 1.23:1 became 1.45:1, and the rule the comment
   itself cited asks for 3:1 -- the repair was a step in the right direction reported as arrival,
   which is worse than leaving it alone, because a row in the register then says it is done.
   Computed 2026-09-05 rather than eyeballed. `--control-edge` clears it at 3.57:1, and this
   boundary earns the token: it carries the only standing signal that the list continues below. */
#symClasses.symscrolls{
  border: 1px solid var(--control-edge);
  border-radius: var(--round);
  padding: 4px;
}

#symFilter{ flex: 1 1 180px; min-width: 120px; }
#symFilterRow .rowmeta{ flex: 0 0 auto; font-variant-numeric: tabular-nums; }

/* ------------------------------------ a class row keeps to one line
   <b>Measured 2026-09-04: the same row is 46 pixels at 1440 wide and 86 at 1280.</b> The row
   wraps when its label and two boxes stop fitting, which doubles its height — and a list bounded
   at ten rows then wants 860 pixels, taller than the window it is in. The row shrinks instead:
   the value and label boxes give up width down to nothing rather than the row giving up a line.
   A colour swatch and a remove button do not shrink, and should not: they are already small
   enough to be hard to hit. */
.setting.symclass{ flex-wrap: nowrap; }
.setting.symclass .symvalue,
.setting.symclass .symlabel{ flex: 1 1 60px; min-width: 0; }
.setting.symclass .q{ white-space: nowrap; }

/* -------------------------- an opacity box beside a colour, on both surfaces
   <b>Narrow, because it holds at most five characters.</b> A per-cent box the width of the
   number boxes beside it reads as another measurement of the same importance as the colour, and
   it is not: it qualifies the swatch to its left. */
.symalpha,
#symVaryFromAlpha,
#symVaryToAlpha{ width: 4.75em; font-variant-numeric: tabular-nums; }

/* <b>A number and its unit are one flex item, not two.</b> They were two, and a row short of
   width broke between them: measured by a design review at 1280 wide, where the symbol stack's
   panel is 359 px and the *%* of an opacity wrapped onto a line of its own, under its own box.
   Gluing them is the fix that holds at any width, rather than a `nowrap` that turns a wrap into
   an overflow — which is what the first attempt did here, pushing the ↑ ↓ × buttons out past the
   row's own border by 125 px. */
.pair{ display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.pair > input{ width: 4.75em; }

/* <b>These two rows wrap, and that is the right answer for them.</b> The class list does not:
   it is bounded to ten visible rows against a classification of 256, so a row that doubles its
   height halves what anybody can see. A symbol has two or three layers and the vary form has two
   rows, so a second line costs a reader nothing and an overflowing row costs them the controls.
   The difference is the length of the list, not the shape of the row. */
.setting.symlayer{ flex-wrap: wrap; row-gap: 6px; }
.setting.symvarystop{ row-gap: 6px; }
.setting.symvarystop input[type="number"]{ width: 5.5em; }

/* ══════════════════════════════════════════════ the 1c editor's own components
   Everything above this line was written for the two-column editor and most of it still
   applies; what follows is only what the three-column one adds or has to say differently.
   Kept together and last, so the difference between the two is readable as a block rather
   than as edits scattered through a thousand lines. */

/* --------------------------------------------------------------- a class row, restated
   <b>Swatch, then the two texts stacked, then the way to remove it.</b> The old row put a
   label word, a value box, a label box, a colour and two buttons on one line — six things
   across 620 pixels, which became six things across 306 when the inspector took over. The
   label and the value are the same fact at two levels, so they stack; the colour comes
   first because a list of classes is read as a list of colours. */

.setting.symclass {
  display: grid; grid-template-columns: 26px minmax(0, 1fr) 20px;
  gap: var(--gap-2); align-items: center;
  padding: 6px 8px; margin: 0;
  border-bottom: 1px solid #eef1f5;
}
.setting.symclass:last-child { border-bottom: 0; }

.setting.symclass .symtexts { min-width: 0; display: flex; flex-direction: column; gap: 2px; }

/* <b>Boxes that do not look like boxes until they are used.</b> Two bordered inputs a row,
   ten rows deep, is a wall of rectangles; the full border arrives on hover and focus, which is
   when it is telling somebody something. The text is still selectable and the control is
   still an input — this is a border, not a disguise.

   <b>But *no* affordance at rest was a different thing, and it was
   [D-225](../../../docs/architecture-debt.md).</b> SC 1.4.11 asks 3:1 of the visual information
   that identifies a control, and an outline that appears on hover satisfies it for nobody: a
   pointer is how you find out, and a keyboard user tabbing through, or anyone scanning the
   screen, never hovers. The row's own trigger asked for this to be decided as a design question
   rather than a contrast one — *raising a contrast that is not there is not a repair* — and
   named the two quiet shapes: a baseline rule, or a fill a shade off the row.

   <b>The baseline rule, because it is one line instead of four.</b> It keeps the reason the
   border was dropped — ten rows of rectangles would be the loudest thing on a screen whose
   subject is colour — while saying *you can type here* without being asked. It is the filled
   text field's affordance, and it is `--control-edge`, so it carries D-215's measured 3.57:1 on
   `--surface`, 3.24 on `--surface-2` and 3.01 on `--surface-3` rather than a value chosen here
   by eye. The transparent border on the other three sides is what keeps hover from moving
   anything. */
.setting.symclass .symlabel,
.setting.symclass .symvalue {
  width: 100%; min-width: 0; flex: none;
  padding: 2px 5px; background: none;
  border: 1px solid transparent;
  border-bottom-color: var(--control-edge);
  border-radius: 3px;
}
.setting.symclass .symlabel { font-size: var(--t-small); color: var(--ink); }
.setting.symclass .symvalue {
  font: 11.5px var(--mono); color: var(--muted);
}
/* <b>The arriving border is the one that has to be seen.</b> It was `--rule-strong` at 1.45:1,
   which after 2026-09-05 was *lighter* than the resting border of every other box on the page --
   so hovering one of these made it fainter than a control that had done nothing. Whether they
   should be borderless at rest at all is a separate question and a separate row:
   [D-225](../../../docs/architecture-debt.md). */
.setting.symclass .symlabel:hover, .setting.symclass .symvalue:hover,
.setting.symclass .symlabel:focus, .setting.symclass .symvalue:focus {
  border-color: var(--control-edge); background: var(--surface);
}

.setting.symclass .symname { font-size: var(--t-small); }

.setting.symclass .symdrop {
  border: 0; background: none; color: var(--faint); min-width: 0; padding: 0;
}
.setting.symclass .symdrop:hover { color: var(--alert); background: none; }

/* <b>3px, and it was 2.</b> The row has a hairline under it now, so a 2px edge read as part
   of the grid rather than as the mark on one row of it. The handoff says 3. */
.setting.symclass.symchosen {
  background: var(--accent-soft); border-radius: 0;
  box-shadow: inset 3px 0 0 var(--accent);
  padding-left: 8px;
}

/* -------------------------------------------------------------- the swatch has a shape
   <b>The inner swatch of a colour input is stylable, and that is what makes this cheap.</b>
   Shaping the element itself would shrink the target — a 12-pixel-tall line swatch is a
   12-pixel-tall click — so the box stays 26 by 22 and the padding decides what is drawn
   inside it: a bar for a line, a rounded rectangle for an area, a disc for a point. */

.setting input[type="color"].symfill { width: 26px; height: 22px; flex: none; }

input[type="color"].sw-line { padding: 8px 2px; }
input[type="color"].sw-poly { padding: 3px; }
input[type="color"].sw-point { padding: 3px; border-radius: 50%; }

input[type="color"].sw-line::-webkit-color-swatch { border: 0; border-radius: 1px; }
input[type="color"].sw-poly::-webkit-color-swatch { border-radius: 2px; }
input[type="color"].sw-point::-webkit-color-swatch { border-radius: 50%; }
input[type="color"].sw-line::-moz-color-swatch { border: 0; border-radius: 1px; }
input[type="color"].sw-poly::-moz-color-swatch { border-radius: 2px; }
input[type="color"].sw-point::-moz-color-swatch { border-radius: 50%; }

/* The legend's swatch is the same three shapes, drawn rather than editable. */
.legendsw { display: block; flex: none; background: var(--sw); }
.legendsw.sw-line { width: 20px; height: 3px; border-radius: 1px; }
.legendsw.sw-poly {
  width: 18px; height: 12px; border-radius: 2px; border: 1px solid rgba(16, 24, 38, .25);
}
.legendsw.sw-point {
  width: 12px; height: 12px; border-radius: 50%;
  box-shadow: 0 0 0 1px rgba(16, 24, 38, .3);
}

/* ------------------------------------------------------------------- the class list box
   Inside the inspector the list is a bordered block rather than a run of rows, because the
   symbol stack under it is a block too and two unframed lists would read as one. */
#insp-classes #symClasses {
  border: 1px solid var(--rule); border-radius: 6px; overflow: auto;
}
#insp-classes #symClasses.symscrolls { border-color: var(--control-edge); padding: 0; }
#insp-classes #symClasses .hint { margin: 0; padding: 10px; }

#insp-classes #symFilterRow, #insp-classes #symAllRow { gap: var(--gap-2); }
#insp-classes #symClassActions { margin: var(--gap-2) 0 var(--gap-3); }

/* --------------------------------------------------------- the stack, in 336 pixels
   The handoff's row: kind, colour, the measurement and its unit, then the three buttons.
   One line, because the panel is short and a stack that wraps is taller than the list
   above it. */
.symstack .setting.symlayer {
  display: grid; grid-template-columns: 52px 26px minmax(0, 1fr) auto;
  gap: var(--gap-2); align-items: center; flex-wrap: nowrap;
  padding: 7px 9px; margin-bottom: 6px;
}
.symstack .setting.symlayer .symlayerkind { min-width: 0; font-size: 12.5px; }
/* ADR-099: a picture marker's row shows the picture where another layer shows its colour. */
.symstack .setting.symlayer .sympicture {
  width: 22px; height: 22px; object-fit: contain;
  border: 1px solid var(--rule); border-radius: 3px; background: var(--surface);
}
.symstack .setting.symlayer .symmeasures {
  display: flex; gap: 6px; align-items: center; min-width: 0;
}
.symstack .setting.symlayer .pair > input { width: 3.6em; }
.symstack .setting.symlayer .tiny { min-width: 24px; padding: 2px 4px; }
.symstack .setting.symlayer .symup { margin-left: 0; }
.symstack .symbuttons { display: flex; gap: 4px; }
.symstack #symStackActions { gap: 6px; margin: var(--gap-2) 0 0; }
.symstack .hint { margin: 0; }

/* --------------------------------------------------------------- the rail's own widths */

#symClassifyRow { display: flex; align-items: center; gap: var(--gap-2); flex-wrap: wrap; }
#symClassifyRow .q { flex: 1 1 100%; }
.symclassify { display: flex; gap: 6px; flex: 1 1 auto; min-width: 0; }
.symclassify #symClassCount { width: 3.4em; flex: none; text-align: center; }
.symclassify #symMethod { flex: 1 1 auto; min-width: 0; }

/* Three across, which is what a 232-pixel content width holds without a name clipping. */
.symrail #symGallery { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; margin: 0; }
.symrail .symcard { padding: 6px 4px 5px; font-size: 10.5px; }
.symrail .symcard svg { height: 30px; }

.symrail .setting.symvarystop { display: flex; flex-wrap: wrap; gap: 6px; }
.symrail .setting.symvarystop .q { flex: 1 1 100%; margin: 0; }
.symrail .setting.symvarystop input[type="number"] { width: 4.6em; }

/* ------------------------------------------------------------------- what is underneath
   Three grounds for a transparent picture. `none` is the checker every empty thumbnail in
   this console already uses, so *no ground* looks like the same absence everywhere. */
.sympreview.ground-light { background: #f2f5f8; }
.sympreview.ground-dark { background: #131a26; }
.sympreview.ground-none {
  background: repeating-linear-gradient(135deg, #eef2f6 0 6px, #e6ebf1 6px 12px);
}

/* On the dark ground the overlays would be four white cards on a night map; they keep their
   contrast and lose their brightness. */
.sympreview.ground-dark .symcap,
.sympreview.ground-dark .symlegend { background: rgba(19, 26, 38, .88); color: #cfd6e2; }
.sympreview.ground-dark .symcap { box-shadow: 0 0 0 1px rgba(255, 255, 255, .14); }
.sympreview.ground-dark .symlegend { box-shadow: 0 0 0 1px rgba(255, 255, 255, .14); }
.sympreview.ground-dark .symlegend em { color: #8e98a8; }

/* --------------------------------------------------------------------- the banners
   The refusal is red because the server said no; the override is amber because nothing
   failed — what is on the screen is simply not what a map draws. */
.symbanner.note {
  background: var(--warn-soft); border-bottom-color: var(--warn-line); color: var(--warn);
}
.symbanner .tiny { border-color: currentColor; color: inherit; background: none; }

/* <b>Neutral, because nothing is wrong</b> — ADR-075, D-271. Reading somebody else's layer is an
   ordinary state, and the amber above already means *what you see is not what a map draws* on this
   same page; a design review read the ownership band as that warning. The surface the unauthored
   note uses, which is the page saying a plain fact about itself. */
.symbanner.note.ownership {
  background: var(--surface-2); border-bottom-color: var(--rule); color: var(--muted);
}

#symUnauthored {
  flex: none; margin: 0;
  padding: 10px var(--gap-5);
  background: var(--surface-2); border-bottom: 1px solid var(--rule);
}

/* ---------------------------------------------------------- what the rail does not need
   `.setting .q { flex: 1 }` and the 104-pixel minimum above were written for a form 620
   pixels wide. In a 232-pixel column they put every label on its own line anyway and then
   held a control at 104 pixels from the left of nothing. */
#page-symbology .symrail .setting .q,
#page-symbology .insppane .setting .q { min-width: 0; }
#page-symbology .insppane .setting .q { flex: 0 0 auto; }

/* --------------------------------------------------------------- the corrections pass
   Measured on the running fixture set at 1440×900, 2026-09-04. Each of these was visible
   in a screenshot rather than argued about. */

/* <b>The panel's own bottom margin, on a panel that is the window.</b> `.panel` carries 16
   pixels so panels stack; this one is a screen, and 16 pixels below the fold is a scrollbar
   on a page that fits. Measured: `document.scrollHeight` 916 against a 900-pixel window. */
#app.symfull #edit { margin-bottom: 0; }

/* <b>Left, and mono only where a value is mono.</b> `.setting input { text-align: right }` is
   right for a form of numbers with units beside them and wrong for a column of names: every
   class label was pinned to the right edge of its own box, so a list of 256 read as ragged
   text with a gap down the left. */
.setting.symclass input:not([type=color]),
.symrail .setting select,
.symrail .setting input:not([type=checkbox]):not([type=color]),
.insppane .setting input:not([type=checkbox]):not([type=color]) { text-align: left; }

.setting.symclass .symlabel { font-family: var(--sans); }
.symrail .setting select { font-family: var(--sans); }

/* The two rail rows that are a number with a unit keep the right-hand alignment a measurement
   wants; the exception above is for names, not for figures. */
.symclassify #symClassCount, .insppane .pair > input { text-align: right; }

/* <b>Two columns when the renderer is one this form cannot author.</b> A `display: none` grid
   item is not an empty cell, it is no cell — so hiding the rail moved the picture into its
   264 pixels and left the inspector's 336 blank. */
.symcols.noRail { grid-template-columns: minmax(0, 1fr) 336px; }

/* The caption and the ground chips share the top of the picture, so neither may take all of
   it. At 264 pixels — which is what the column is while a Proportional renderer is open —
   they were on top of each other. */
.symcap { max-width: 46%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.symground { max-width: 50%; }

.insppane .docfoot > span { min-width: 0; }
.insppane .docfoot .tiny, .insppane .dochead .tiny { flex: none; margin-left: auto; }

/* The generated swatch on the empty screen is the layer's own colour, not a grey box: the
   sentence beside it says the colour is derived from the layer's identity, and a hatched
   rectangle is the one thing on the screen that could have shown it. */
.symempty .sw i {
  display: block; background: var(--sw, var(--muted));
  box-shadow: 0 0 0 1px rgba(16, 24, 38, .25);
}
.symempty .sw i.sw-line { width: 70px; height: 4px; border-radius: 2px; box-shadow: none; }
.symempty .sw i.sw-poly { width: 62px; height: 40px; border-radius: 3px; }
.symempty .sw i.sw-point { width: 26px; height: 26px; border-radius: 50%; }

/* <b>The swatch fills the box it is in.</b> Chromium wraps a colour input's swatch in its own
   padded element, so a 26×22 control drew a 20×9 chip inside six pixels of nothing and the
   three shapes were indistinguishable from each other. The wrapper's padding goes; the input's
   own padding is what makes a bar, a box or a disc of it. */
input[type="color"].symfill::-webkit-color-swatch-wrapper { padding: 0; }
input[type="color"].symfill::-webkit-color-swatch { border: 1px solid rgba(16, 24, 38, .2); }

/* <b>The symbol stack is a footer, not the end of a scroll.</b> Ten class rows at 58 pixels
   each is 580, and the pane is about 700 — so on the fixture this screen was reported against
   the panel naming the class you are editing sat thirty pixels below the fold, which is the
   fault D-217 was about wearing a different shape. The list gives up the room instead.

   <b>One scroller, not two.</b> The list is the flexible child of a column that does not
   scroll, so a short list is its own height and a long one stops where the panel does. Putting
   the scroll on the wrapper instead would nest a ten-row scroller inside a pane scroller, and
   nobody can work out which of two bars moves what. */
#insp-classes { overflow: hidden; }
#insp-classes > .pad {
  flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden;
  padding-bottom: var(--gap-3);
}
#insp-classes #symFilterRow,
#insp-classes #symAllRow,
#insp-classes #symClassActions { flex: none; }
#insp-classes #symClasses { flex: 0 1 auto; min-height: 0; overflow: auto; }
#insp-classes .symstack { flex: none; margin-top: 0; }

/* <b>The strip's right-hand end is not allowed to leave the screen.</b> Measured on a
   three-layer service: the switcher's chips took 420 pixels, the tabs 400, and Store was 130
   pixels past the right edge with nothing to scroll it back. Store is the only control on this
   screen that cannot be reached another way, so it is the one thing that never shrinks; the
   breadcrumb gives up characters first, then the switcher scrolls. */
/* <b>The crumb still abbreviates before the controls do.</b> It shared the strip with a
   segmented layer switcher until the handoff's revision moved that into the rail; what is left
   is context, and context is the thing that can be shortened. `flex-basis: 0` keeps it to the
   room the tabs and the two acts leave over. */
.symstrip .crumbs {
  min-width: 60px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 1 1 auto;
}
.symstrip .segmented.tabs { flex: none; }
.symstrip .symdo { flex: none; }

/* <b>Two measurements and three buttons in 306 pixels.</b> A stroke row carries a width, a
   unit, an opacity, a per-cent sign and the three controls that reorder and remove it; laid out
   as one unbroken line the opacity box came out four pixels wide. The measurements wrap inside
   their own cell instead, which keeps the buttons where they are down the stack. */
.symstack .setting.symlayer .symmeasures { flex-wrap: wrap; row-gap: 4px; }
.symstack .setting.symlayer .pair > input { width: 3.9em; }
.symstack .setting.symlayer .tiny { min-width: 22px; padding: 1px 3px; }
.symstack .symbuttons { gap: 2px; }

/* A classify row that is only its button — which is what a unique-value renderer's is — reads
   as a button somebody forgot to label when it is pushed to the right by a label that is not
   there. */
#symClassifyRow #symClassify { margin-right: auto; }

/* <b>Fitting 1,370 pixels of strip into 1,208.</b> Measured at 1440 on a three-layer service.
   The tabs give up horizontal padding first — they are five short words and were spending 410
   pixels on them — and the state line is capped with its whole sentence in a tooltip, because
   it is the one child of the strip that is prose. Store and *Back to generated* do not shrink:
   they are the two acts this screen exists for. */
/* <b>The same size as the item page's, because they are the same five tabs.</b> This strip drew
   them at 13.5px and the page head at 12.5; a reader crossing between the two screens met one
   control that had quietly changed scale. */
.symstrip .segmented.tabs a { padding: 4px 9px; font-size: 12.5px; }
.symstrip .symstate {
  max-width: 190px; overflow: hidden; text-overflow: ellipsis;
}

/* One symbol is not a list, so its pane does not reserve a list's worth of height. */
#insp-classes.onesymbol { overflow: auto; }
#insp-classes.onesymbol > .pad { flex: none; overflow: visible; }

/* ═══════════════════════════════════════════════════ the item page, handoff 2026-09-04 */

/* <b>The service's tabs are a segmented control now, beside its title.</b> `.tabstrip` is still
   right where it is used — a scope strip over a table, a group's sections — but this one moves
   between the five faces of one item, which is the same act the layer editor's strip performs,
   and two shapes for one act is D-46's whole subject. `solid` is the handoff's dark fill: these
   tabs sit on `--paper` rather than inside a panel, so the raised-white chip the other segmented
   controls use has nothing to be raised from. */
.segmented.solid a[aria-current] {
  background: var(--ink); color: var(--surface); box-shadow: none;
}

.pagehead .segmented { align-self: center; }

/* Two cards side by side, and one column when there is not room for two. */
.cardpair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--gap-4); }
.cardpair .panel { margin-bottom: 0; }
.cardpair h4 { margin-top: 0; }
@media (max-width: 1180px) { .cardpair { grid-template-columns: minmax(0, 1fr); } }

.pills { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: var(--gap-3); }

/* <b>Offered and not offered, both shown.</b> A list of what is on says nothing about what is
   off, and *why can this client not edit* is the question somebody has. The greyed pill keeps
   `--faint`, which is 4.54:1 on the palest surface it sits on — a state that cannot be read is
   not a lighter version of one that can. */
.pill.p-on { background: var(--accent-soft); border-color: var(--accent-line); color: var(--link); }
.pill.p-off { background: var(--surface-2); border-color: var(--rule); color: var(--faint); }
.pill.p-off::before { opacity: .45; }

/* --------------------------------------------------------- the Overview layers table
   Five columns: the id every address uses, the shape the layer is, its name, whether anybody
   has styled it, and the three ways in. */
#serviceLayerRows .lid {
  width: 30px; font-family: var(--mono); font-size: 12.5px; color: var(--muted);
  text-align: right; vertical-align: middle;
}
#serviceLayerRows .lsw { width: 44px; vertical-align: middle; }
/* Three narrow columns fit their card: the shared 640-pixel floor made a 384-pixel card scroll sideways, and a
   scrolling region nobody can focus (round 2). The name wraps instead. */
#serviceLayersPanel.widetable { overflow-x: visible; }
#serviceLayersPanel.widetable table { min-width: 0; width: 100%; }
#serviceLayerRows td.name { overflow-wrap: anywhere; }
#serviceLayerRows .lstate { width: 160px; vertical-align: middle; }
#serviceLayerRows .lstate .authored { color: var(--link); font-weight: 600; }

.geoswatch {
  display: grid; place-items: center; height: 30px; border-radius: 4px;
  background: repeating-linear-gradient(135deg, #eef2f6 0 5px, #e6ebf1 5px 10px);
}
.geoswatch::before { content: ""; display: block; background: var(--sw, var(--muted)); }
.geoswatch.sw-line::before { width: 26px; height: 3px; border-radius: 2px; }
.geoswatch.sw-poly::before {
  width: 24px; height: 15px; border-radius: 2px; border: 1px solid rgba(16, 24, 38, .25);
}
.geoswatch.sw-point::before {
  width: 12px; height: 12px; border-radius: 50%; box-shadow: 0 0 0 1px rgba(16, 24, 38, .3);
}

/* <b>Three buttons, not two links run together.</b> Measured before the change: *Symbology* and
   *document* sat side by side with a space between them, which reads as one target of 96 pixels
   holding two. A resting border is what makes three of them three. */
#serviceLayerRows .acts { white-space: nowrap; }
#serviceLayerRows .acts .tiny {
  border: 1px solid var(--rule); background: var(--surface); border-radius: 4px;
  padding: 3px 9px; margin-left: 6px; color: var(--ink); font-size: 12.5px;
}
#serviceLayerRows .acts .tiny:hover { border-color: var(--accent); color: var(--link); }

/* <b>A blank cover explains itself.</b> The picture arrived and is empty, which is not the same
   as a picture that failed — and a white rectangle where a map should be reads as the second. */
.thumb.empty.drewnothing {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 4px; padding: 0 14px; text-align: center;
  font-size: 12px; line-height: 1.5; color: var(--faint);
}
.thumb.empty.drewnothing b { color: #33404f; font-size: 13px; }

/* The Visualization toolbar's own row: a label, the layer strip, what that layer is, and the
   way to its symbology. `.toolbar` already lays these out; these only stop the strip from
   stretching and give the meta a resting colour. */
.toolbar .segmented { flex: 0 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.toolbar .segmented::-webkit-scrollbar { height: 0; }
#visMeta { color: var(--muted); font-size: var(--t-small); white-space: nowrap; }

/* <b>A hover ring, so the picture reads as the target it is.</b> The thumbnail in a content row
   opens the map; without a resting affordance it is a picture that happens to be clickable,
   which is a thing people find by accident or not at all. */
.thumblink { display: block; border-radius: 5px; }
.thumblink:hover .thumb, .thumblink:focus-visible .thumb {
  box-shadow: 0 0 0 2px var(--accent);
}
.thumblink:focus-visible { outline: none; }

/* ------------------------------------------------------- operations, handoff 2026-09-04
   Three cards across rather than three sections down, then the two short blocks side by side.
   The route table stays full width below both: its patterns do not wrap and it needed 1,407
   pixels the last time anybody measured it. */
/* Four cards since D-237 added Datastore, and the basis came down from 300px so the fourth
   fits beside the others at 1440 rather than sitting alone under the first with the rest of
   its row empty — design review 2026-09-11. 1100 and 400 still give two and one. */
.opscards {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--gap-4); margin-bottom: var(--gap-5);
  align-items: start;
}
.opscard { margin-bottom: 0; display: flex; flex-direction: column; }
.opscard h4 {
  margin: 0 0 var(--gap-3); display: flex; align-items: center; gap: var(--gap-2);
  padding-bottom: var(--gap-2); border-bottom: 1px solid var(--rule);
}
.opscard .metrics { margin: 0; }
.opscard .hint { margin: var(--gap-3) 0 0; }
.opscard .hint:empty { display: none; }

.opspair {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
  gap: var(--gap-5); align-items: start; margin-bottom: var(--gap-5);
}
.opspair .label:first-child { margin-top: 0; }
.opspair .dot { margin-right: 6px; }

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

/* <b>A dot with a class, because `#healthDot` was the only one and it was styled by id.</b> The
   operations cards need the same indicator and copying four rules under a second id is how a
   product ends up with two greens. The dot tokens are the indicator pair the token block
   defines — `--ok` is the text colour and `--ok-dot` is the dot, and the reason is written
   there: dark enough to read and dark reads as muddy on an arc. */
.dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--faint); vertical-align: middle;
}
.dot.ok { background: var(--ok-dot); }
.dot.degraded { background: var(--warn-dot); }
.dot.unusable { background: var(--alert-dot); }
.dot.unknown { background: var(--rule-strong); }

/* <b>A label and its figure on one line, which a bare `dl` does not do.</b> `dl.metrics` had no
   rules at all: the browser's default put every value on its own line indented 40 pixels, so a
   card of five figures was ten lines with a staircase down the middle. Measured on the
   operations cards, 2026-09-04. */
.metrics { display: flex; flex-direction: column; gap: 7px; margin: 0; }

/* <b>The grid is on the row, not on the list.</b> `metric()` wraps each pair in a `div`, so a
   grid on the `dl` would lay out the wrappers and leave every `dt`/`dd` stacked inside one. */
.metric {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 2px var(--gap-3); align-items: baseline;
}
.metric dt { color: var(--muted); font-size: var(--t-small); min-width: 0; }
.metric dd {
  margin: 0; text-align: right; font-weight: 600; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The note is a second `dd`, and it belongs under both columns rather than beside the figure. */
.metric dd.val {
  grid-column: 1 / -1; text-align: left; font-weight: 400; white-space: normal;
  color: var(--faint);
}
.metric dd small { font-weight: 400; color: var(--muted); font-size: 11.5px; margin-left: 2px; }

/* <b>The override is a footer of the symbology panel, not a panel of its own.</b> It is the
   exception to every row above it — unless one of these is stored, none of them is what the tile
   face draws — and an exception is read under the rule it qualifies. */
.styleoverride {
  margin: var(--gap-4) calc(var(--gap-5) * -1) calc(var(--gap-4) * -1);
  padding: var(--gap-4) var(--gap-5);
  border-top: 1px solid var(--rule); background: var(--surface-2);
  border-radius: 0 0 var(--round-lg) var(--round-lg);
}
.styleoverridehead {
  display: flex; align-items: flex-start; gap: var(--gap-4); flex-wrap: wrap;
}
.styleoverridehead .row { margin: 0 0 0 auto; flex: none; }
.styleoverride .hint { margin: 2px 0 0; }
.styleoverride #styleAdvanced { margin-top: var(--gap-3); }

/* ------------------------------------------------------- a member's initials, 2026-09-04
   <b>No image and no request.</b> Two letters on a tinted square, the tint derived from the
   name — the same determinism the generated symbology uses, so the square somebody learns is
   the same square tomorrow. Ink stays `--ink` at every hue because only the hue moves. */
.whorow { display: flex; align-items: center; gap: var(--gap-3); min-width: 0; }
.avatar {
  width: 28px; height: 28px; flex: none; border-radius: 6px;
  display: grid; place-items: center;
  background: var(--avatar, var(--surface-3));
  box-shadow: inset 0 0 0 1px rgba(16, 24, 38, .08);
  font: 600 11.5px/1 var(--sans); color: #33404f; letter-spacing: .02em;
}

/* <b>The role is a pill again, as the handoff drew it</b> — admin blue, publisher violet, other grey.
   It was a select that saved on an arrow key (UX sweep 2026-10-19); the change is in the row menu now,
   so the row only reads. No dot: the word is the whole of it. */
.pill.rolepill::before { display: none; }
.pill.rolepill[data-role-is="administrator"] {
  color: var(--accent-ink); border-color: var(--accent-line); background: var(--accent-soft);
}
.pill.rolepill[data-role-is="publisher"] {
  color: var(--violet); border-color: var(--violet-line); background: var(--violet-soft);
}

/* Change role… / Change user type…: the choices as radio rows, and what the chosen one does under them,
   amber when it takes something away. A full border, never a side stripe. */
fieldset.pickset { border: 0; padding: 0; margin: 0 0 var(--gap-4); min-width: 0; }
fieldset.pickset legend { font-weight: 600; margin-bottom: var(--gap-2); padding: 0; }
.consequence { margin: 0; padding: var(--gap-3) var(--gap-4); border: 1px solid var(--rule);
               border-radius: var(--round); background: var(--surface-2); font-size: var(--t-small); }
.consequence.lowers { border-color: var(--warn-line); background: var(--warn-soft); }
.consequence b { color: var(--ink); }

/* -------------------------------------------------------- the sharing scope, as cards
   <b>Three answers with a sentence each.</b> A select can carry a word, so the sentences were
   three paragraphs under it — which is where an explanation goes when the control has no room
   for one, and it means the reader chooses before reading. The chosen card takes a 2px accent
   border and the pale accent ground, which is the same *this one* the class list uses. */
.scopecards { display: flex; flex-direction: column; gap: var(--gap-2); max-width: 620px; }

.scopecard {
  display: flex; gap: var(--gap-3); align-items: flex-start;
  padding: 10px 12px; border: 1px solid var(--rule); border-radius: 6px;
  background: var(--surface); cursor: pointer;
}
.scopecard:hover { border-color: var(--rule-strong); }
.scopecard:has(input:checked) {
  border: 2px solid var(--accent); padding: 9px 11px; background: var(--accent-soft);
}
.scopecard:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.scopecard input { margin: 3px 0 0; flex: none; accent-color: var(--accent); }
.scopecard > span { min-width: 0; }
.scopecard b { font-weight: 600; }
.scopecard .said { display: block; color: var(--muted); font-size: 12.5px; margin-top: 1px; }

/* <b>The stack's colour box is 26 pixels because its grid column is.</b> `.setting
   input[type="color"]` is 44 wide, which suits a form row and not a 306-pixel panel: squeezed
   into the column it drew a grey bar with no colour visible in it. And the width boxes take one
   more character, because a stroke width is `0.75` and `0.` is not a number. */
.symstack .setting.symlayer input[type="color"] { width: 26px; height: 22px; padding: 2px; }
.symstack .setting.symlayer input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.symstack .setting.symlayer .pair > input { width: 4.9em; }

/* <b>The last digit shows, 2026-09-25.</b> In 306 pixels the width and the opacity still take
   two lines, as the rule above chose; what was wrong is that the number's spinner ate the last
   digit of `0.75`, and each unit reserved `.setting .u`'s 68 pixels — a form row's column — beside
   a box in a panel. The unit takes its own width and the boxes drop the spinner; arrow keys still
   step the value. */
.symstack .setting.symlayer .u { width: auto; }
.symstack .setting.symlayer .pair > input { width: 3.6em; padding-left: 5px; padding-right: 5px;
                                             -moz-appearance: textfield; appearance: textfield; }
.symstack .setting.symlayer .pair > input::-webkit-inner-spin-button,
.symstack .setting.symlayer .pair > input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

/* <b>The Map shortcut costs 42 pixels the services table did not have, and it still does.</b>
   Measured at 1440: without the shortcut the table is 816 wide in an 816-pixel column beside the
   folder rail — exactly full — and with it, 858. Tightening the row's own padding is what this
   block does and it buys back none of the difference, because the columns were already at their
   content width; the honest statement is that this table now scrolls by 42 pixels at 1440 and
   that `.widetable` is the mechanism that lets it. The alternative was hiding the shortcut in
   the overflow menu, which is where the handoff explicitly did not put it. */
#services .acts { white-space: nowrap; }
#services .acts .tiny, #services .acts .menu > summary { padding: 3px 7px; }
#services .acts .tiny + .tiny, #services .acts .tiny + .menu { margin-left: 4px; }
#services .acts .ico { margin-right: 3px; }

/* ═════════════════════════════════ the rail, handoff revision 2026-09-04
   Three changes, all of them about the same thing: a 264-pixel column was reading as a wall of
   controls. Which layer moves into it from the title strip, because it is the editor's first
   question and not a place; the two blocks a reader has usually not asked for fold shut and say
   what they hold on their own row; and the service-wide override becomes a footnote at the
   bottom rather than a panel on a page of its own. */

/* --------------------------------------------------------------- which layer, as cards
   Each entry carries the geometry as a swatch and whether anybody has styled it, so choosing
   between three layers does not mean opening three of them. Drawn even at one layer: a heading
   that says *this is what you are editing* still has an answer there, and a segmented control
   of one did not. */
#symLayerPick { display: flex; flex-direction: column; gap: 4px; }

.symlayerpick {
  display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: 10px; align-items: center;
  padding: 7px 9px; border: 1px solid var(--rule); border-radius: 6px;
  background: var(--surface); color: var(--ink); text-decoration: none;
}
.symlayerpick:hover { border-color: var(--rule-strong); text-decoration: none; }
.symlayerpick.on {
  border: 2px solid var(--accent); padding: 6px 8px; background: var(--accent-soft);
}
.symlayerpick:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.symlayerpicktext { min-width: 0; }
.symlayerpickname {
  display: block; font-size: var(--t-small);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.symlayerpick.on .symlayerpickname { font-weight: 600; }
.symlayerpick .rowmeta { display: block; font-size: 12px; color: var(--muted); }
.symlayerpick .rowmeta.authored { color: var(--link); font-weight: 600; }
.symlayerpick .geoswatch { height: 26px; }

/* ------------------------------------------------------------- a fold, and its summary
   <b>A disclosure owes a summary.</b> A row that says only *Vary with a number* asks the reader
   to open it to find out whether anything is varying, which is exactly the cost folding was
   meant to remove. */
.symfold { display: block; }

.symfoldhead {
  display: flex; align-items: center; gap: var(--gap-2); width: 100%;
  padding: 9px 11px; border: 1px solid var(--rule); border-radius: 6px;
  background: var(--surface); color: #33404f;
  font: inherit; font-size: 13px; text-align: left; cursor: pointer;
}
.symfoldhead:hover { border-color: var(--accent); }
.symfoldhead .caret { color: var(--faint); flex: none; }
.symfoldsays {
  margin-left: auto; font-size: 12px; color: var(--ink); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 55%;
}
.symfoldsays.quiet { color: var(--faint); }

.symfoldbody { margin-top: var(--gap-2); }
.symfoldbody[hidden] { display: none; }

/* ------------------------------------------------- the service's own style, as a footnote
   It is a MapLibre document for the whole service — an expert control that most services do not
   have — so it sits under everything about this one layer, at the size a footnote is, and its
   editor opens only when somebody asks for it. */
.symoverride {
  margin-top: auto; padding-top: var(--gap-4);
  border-top: 1px solid var(--rule);
  font-size: 12.5px; color: var(--faint); line-height: 1.5;
}
.symoverride > b { display: block; color: #33404f; font-size: 13px; margin-bottom: 3px; }
.symoverride .hint { margin: 0 0 6px; font-size: 12.5px; }
.symoverride .row { margin: 0 0 var(--gap-2); gap: 6px; flex-wrap: wrap; }
.symoverride textarea { width: 100%; font: 11.5px/1.5 var(--mono); }

/* The scope pill is its own child of the strip, so the breadcrumb's ellipsis cannot eat it. */
.symstrip #symScope { flex: none; display: inline-flex; }
.symstrip #symScope:empty { display: none; }

/* <b>The generated screen covers the picture and the inspector, not the rail.</b> The handoff's
   revision moved the layer list into the rail, and a screen about *this* layer's absent document
   must not hide the way to the service's other layers. It takes their two columns and says so
   across both. */
.symcols.generated > .sympreview, .symcols.generated > .syminsp { display: none; }
.symcols.generated > .symempty { grid-column: 2 / -1; }
.symcols > .symempty { border-left: 1px solid var(--rule); background: var(--paper); }

/* ══════════════════════════════════ the preview column is a map, 2026-09-04
   Owner, pointing at two ArcGIS Online Map Viewer videos: the map should open the way theirs
   does, and what the symbology controls change should show on it. The still frame could answer
   *what colour* and never *what does this look like where I am looking* — which is the question
   ADR-051 says an appearance is chosen by.

   <b>The ground pans, the picture is drawn by the server.</b> Two elements: an OpenLayers map
   underneath, and the image the preview endpoint returns for that exact viewport over it. The
   image takes no pointer events, so every gesture reaches the map. */
#symMap { position: absolute; inset: 0; }
#symMap .ol-viewport { border-radius: 0; }

.sympreview > img,
.sympreview > .thumb.empty { pointer-events: none; z-index: 1; }

/* <b>The ground chips choose what OpenLayers draws, not what is behind a transparent PNG.</b>
   With a map underneath, *None* is the honest name for no basemap — the hatched checker that
   used to mean it is what an absent picture looks like, not an absent ground. */
.sympreview.ground-none #symMap { opacity: 0; }
.sympreview.ground-dark #symMap { filter: invert(1) hue-rotate(180deg) saturate(.7) brightness(.85); }

/* The overlays sit above both. */
.symcap, .symground, .symlegend { z-index: 2; }

/* The zoom buttons take the console's own chrome rather than OpenLayers' blue. */
.sympreview .ol-zoom { top: auto; left: 14px; bottom: 14px; z-index: 2; }
.sympreview .ol-zoom button {
  background: var(--surface); color: var(--ink); border-radius: 4px;
  box-shadow: 0 0 0 1px var(--rule); font: 600 15px/1 var(--sans); margin: 0 0 2px;
  width: 26px; height: 26px;
}
.sympreview .ol-zoom button:hover { background: var(--surface-2); color: var(--link); }
/* The legend keeps the bottom-left corner, so it moves up out of the buttons' way. */
.symlegend { bottom: 74px; }

/* ══════════════════════════════════ below the editor's floor, D-221 — owner decision 2026-09-11
   <b>The columns stack and the picture stays on the screen.</b> The owner's answer —
   *"Yeniden dizilsin"*, re-arrange it — and the two alternatives they turned down: a sentence
   saying this screen needs more width, and waiting for somebody to complain. ADR-053 set a floor of 1,180 pixels and nothing answered below it.

   <b>What happened below it was worse than the register said.</b> D-221 recorded that the whole
   editor scrolled sideways. It did not: `#edit` carries `overflow: hidden` for its rounded
   corners, so the three columns were cut off at the window's edge with no scrollbar at all.
   Measured 2026-09-11 with the sidebar open: at 1,300 the inspector and Store were past the edge
   and unreachable; at 1,024 the picture was cut to 528 of its 580 pixels as well; at 768 a
   reader saw 272 pixels of picture and nothing to the right of it.

   <b>Measured against the editor, not the window.</b> The floor is the editor's own width, and the
   window is that plus a sidebar the reader chooses — 232 pixels open, 64 collapsed. A window
   query would have to carry a copy of the sidebar's width inside each breakpoint and a copy of
   each rule per sidebar state, and the sidebar has changed width once already (248 to 232, see
   `#shell`). The idiom is otherwise the one `.pickpair` and `.cardpair` use: a grid gives up a
   column under a width, nothing is hidden, nothing new is drawn. So the numbers here are the
   editor's: three columns from 1,180, which is a 1,412 window with the sidebar open and 1,244
   with it collapsed — exactly where they were, so the wide layout is unchanged wherever it fit.

   <b>Layout containment, and only while the editor is showing.</b> A size container contains its
   own layout, which makes it the reference box for anything fixed-position inside it. Nothing on
   this page is — the row menus that position themselves against the window live on other pages,
   and the dialogs are in the top layer — and `symfull` is set for this page alone. */
#app.symfull #editPages { container: symeditor / inline-size; }

/* <b>Under 1,180: the picture on top of the renderer, the inspector beside both.</b> The picture
   keeps the full width of the column it leaves and the height of most of the screen; the
   inspector keeps exactly the column it had — 336 pixels, window-tall — because it is the one
   whose inside was tuned against a height: the class list gives up room so the symbol stack
   below it stays on the screen (ADR-053 §5), and a short inspector would bring that fault back.
   The rail is the column that moves. It was a scroller before and it still is, now under the
   picture rather than beside it. */
@container symeditor (max-width: 1179px) {
  #app.symfull #page-symbology.on { min-width: 0; }

  .symcols {
    grid-template-columns: minmax(0, 1fr) 336px;
    grid-template-rows: minmax(240px, 56%) minmax(0, 1fr);
    grid-template-areas: "preview insp" "rail insp";
  }
  .symcols > .sympreview { grid-area: preview; }
  .symcols > .symrail { grid-area: rail; border-right: 0; border-top: 1px solid var(--rule); }
  .symcols > .syminsp { grid-area: insp; }

  /* <b>The rail's form keeps a form's width, and the three renderer cards go side by side.</b>
     Design review 2026-09-11, measured at 1,300: under the picture the rail is 732 pixels wide
     and 350 tall, holding 736 of content — the cards and the Field list stretched across all of
     it with an icon and a label at one end, and half the rail was below the box. Side by side the
     cards give back 75 pixels of that height, which at 1,300 brings the Renderer section to
     within eight pixels of the box's bottom; the cap stops every control being as wide as a
     picture. 560 is three cards at 180 and a list at a width somebody can read across. What is
     still below the box there is the two folds and the service-wide override, which the handoff
     folded for being the parts a reader has usually not asked for, and the rail scrolls to them —
     with its own scrollbar, as it always has. At 1,024 by 768 the box is 274 pixels and the
     Field list and *Read the values* are below it too; the picture keeps its height regardless,
     because it is what the page is for. */
  .symcols > .symrail > * { max-width: 560px; }
  .symcols > .symrail .symkinds { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }

  /* No rail, so no second row: the picture takes the height the rail would have had. */
  .symcols.noRail { grid-template-rows: minmax(0, 1fr); grid-template-areas: "preview insp"; }

  /* The generated screen keeps its shape — the layer list, then the sentence across the rest —
     because the columns it covers are the ones that moved. */
  .symcols.generated {
    grid-template-columns: 264px minmax(0, 1fr); grid-template-rows: minmax(0, 1fr);
    grid-template-areas: "rail empty";
  }
  .symcols.generated > .symrail { border-top: 0; border-right: 1px solid var(--rule); }
  .symcols.generated > .symempty { grid-area: empty; }

  /* <b>The strip wraps rather than pushing Store out.</b> Its children want about 970 pixels on
     one line; below that the two acts go to a second line, still at the right-hand end. The
     breadcrumb's basis is zero so that it is the thing that gives way on the first line, which is
     the order `.symstrip .crumbs` already sets for the wide strip. No vertical padding: Store is
     44 pixels and the strip's own minimum, so on one line it touches both edges in the wide
     window too — padding added here made the one-line strip 12 pixels taller than the wide one. */
  .symstrip { flex-wrap: wrap; row-gap: 6px; }
  .symstrip .crumbs { flex: 1 1 0; }
}

/* <b>Under 760: one column, and the picture is pinned to the top of it.</b> The picture beside a
   column of controls would be under 424 pixels here, which is back to ADR-051's thumbnail. So the
   editor becomes one scrolling column — picture, renderer, inspector, in the order they read
   across a wide window — and the picture is `sticky`, so that scrolling down to a control does
   not scroll away the thing the control changes. The inspector is sized to fill what the picture
   leaves, which puts the class list and the symbol stack on the screen together when it is
   scrolled to, as they are in the wide window. */
@container symeditor (max-width: 759px) {
  /* <b>`scroll-padding-top`, because a pinned picture is a pinned header.</b> Anything scrolled
     to from below lands at the top of the scrollport, which is under the picture. The page does
     that itself — *Add a class* calls `scrollIntoView({ block: "nearest" })` on the new row —
     and measured at 768 by 900 a card brought into view that way sat at 177 pixels under a
     picture ending at 480; with the padding it sits at 481. Chrome centres a focused control,
     which happens to clear the picture; a browser that aligns focus to the edge would not. */
  .symcols {
    display: flex; flex-direction: column; overflow-y: auto;
    scroll-padding-top: max(220px, 42%);
  }
  .symcols > * { flex: none; }

  .symcols > .sympreview {
    order: -1; position: sticky; top: 0; z-index: 3;
    height: 42%; min-height: 220px; border-bottom: 1px solid var(--rule);
  }
  .symcols > .symrail { overflow: visible; border-top: 0; border-right: 0; }
  .symcols > .syminsp { height: 58%; min-height: 360px; border-left: 0; border-top: 1px solid var(--rule); }

  /* The generated screen reads first, and the layer list follows it. */
  .symcols > .symempty { order: -1; border-left: 0; border-bottom: 1px solid var(--rule); }
  .symcols.generated > .symrail { border-right: 0; }

  /* The state sentence is the one child of the strip that may shorten; Store and *Back to
     generated* do not. */
  .symstrip .symdo { flex: 0 1 auto; min-width: 0; }
  .symstrip .symstate { flex: 0 1 auto; min-width: 0; }
}

/*
  <b>Visibly out of use rather than gone.</b> When the advanced connection string is filled in
  it is what gets sent, and the fields above it are ignored — a design review found that
  happening silently on 2026-09-06, with a Test failing against a host nowhere on screen. The
  fields are disabled and dimmed instead of hidden, because somebody who typed a host into them
  wants to see it still there when they empty the advanced box again.
*/
label.overridden { opacity: .5; }
label.overridden input { background: var(--surface-2); cursor: not-allowed; }

/* ------------------------------------------------------------------- the composer */
/*
  <b>Three columns, and the middle one is not a map.</b> ADR-057's study drew a preview
  between Contents and Databases; nothing on this server turns an unpublished composition
  into a picture, so what sits there is the service that will exist rather than a control
  for a feature that does not — ADR-034's rule applied to our own design.
*/
/*
  <b>Bounded, since 2026-09-06, and the unbounded version was the whole complaint.</b> The three
  panes were `min-height: 420px` with no ceiling, so the tallest one set the height of the row —
  and the tallest is Databases, which lists every table in every registered database. Sixty
  tables made the page four screens long, the contents pane a strip at the top of an empty
  column, and the preview a 900-pixel drawing adrift in seventeen hundred pixels of nothing. The
  owner's words were *arayüz çok kötü durumda*, and this is what they were looking at.

  <b>The row is a viewport height minus what is above it, and each pane scrolls its own
  contents.</b> A list that can be any length has to scroll inside something; the alternative is
  the page growing to fit the largest database anybody registers, which is not a layout decision
  so much as the absence of one.
*/
.composer {
  display: grid; grid-template-columns: 320px minmax(0, 1fr) 340px; gap: 12px;
  align-items: stretch;
  height: calc(100vh - 174px - var(--header-h, 58px)); min-height: 460px;
}
@media (max-height: 780px) { .composer { height: auto; } }

.composerpane { display: flex; flex-direction: column; min-height: 0; min-width: 0; padding: 0; }
.composerhead {
  display: flex; align-items: baseline; gap: 8px; padding: 9px 12px;
  border-bottom: 1px solid var(--rule); background: var(--surface-2);
}
.composerhead b {
  font-size: var(--t-tiny); letter-spacing: .08em; text-transform: uppercase; color: var(--faint);
}
.composerbody { flex: 1; overflow: auto; padding: 6px 0 12px; }

.pubempty {
  margin: 18px 12px; padding: 26px 14px; text-align: center; color: var(--faint);
  border: 1px dashed var(--rule-strong); border-radius: 4px; font-size: var(--t-small);
}
#pubContents.dropping .pubempty { border-color: var(--accent); color: var(--link); }

.pubnode { margin: 0 8px; border-radius: 3px; }
.pubnode + .pubnode { margin-top: 1px; }
.pubnode.dragging { opacity: .45; }
.pubnode.over { box-shadow: inset 0 2px 0 var(--blue); }
.pubnode.into .pubrow { box-shadow: inset 0 0 0 2px var(--accent); }
.pubnode.sel > .pubrow { background: var(--blue-soft); box-shadow: inset 0 0 0 1px var(--blue-line); }
.pubrow {
  display: flex; align-items: center; gap: 7px; padding: 5px 8px; border-radius: 3px;
  cursor: grab; user-select: none; font-size: var(--t-small);
}
.pubrow:hover { background: var(--surface-2); }
/* <b>The name takes the room and the badge gives it up.</b> Every child of the row had its
   natural width, so `tm30native_ee39df8f` was clipped to `tm30native_ee39…` beside three
   hundred pixels of empty column — the ellipsis was doing its job on a box nothing had sized.
   The title attribute stays for the case where the column really is narrow. */
.pubrow .pubname { flex: 1 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pubrow .pubsr { margin-left: auto; flex: 0 0 auto;
  font: var(--t-tiny)/1 var(--mono); color: var(--faint); }
.pubrow .pubkill { border: 0; background: transparent; color: var(--faint); padding: 0 4px;
  cursor: pointer; font-size: 13px; }
.pubrow .pubkill:hover { color: var(--alert-dot); }
.pubnode.grouped .pubrow { margin-left: 20px; }
.pubgroupname { font-weight: 600; }
.pubcount {
  font: var(--t-tiny)/1 var(--mono); color: var(--faint); background: var(--surface-3);
  border-radius: 8px; padding: 2px 6px;
}

.pubdb, .pubschema, .pubtable {
  display: flex; align-items: center; gap: 7px; padding: 5px 8px; border-radius: 3px;
  font-size: var(--t-small); user-select: none;
}
.pubdb { font-weight: 600; cursor: pointer; margin: 0 8px; }
.pubschema { padding-left: 24px; color: var(--muted); cursor: pointer; margin: 0 8px; }
.pubtable { padding-left: 40px; cursor: grab; margin: 0 8px; }
.pubdb:hover, .pubschema:hover, .pubtable:hover { background: var(--surface-2); }
/* <b>`.used` went with the rule it drew.</b> A table served by another service is an ordinary
   table here since migration 40 — owner decision 2026-09-06. What is left is the one thing that
   really cannot become a layer: no geometry column, or no integer this server can use as an
   object id. */
.pubtable.no { cursor: not-allowed; color: var(--faint); }
/* <b>The name gives way, not the pane.</b> `ci_earlyalert_reports_08d1a14c` beside a reference
   badge is wider than a 340-pixel column, and with nothing allowed to shrink the row pushed a
   horizontal scrollbar across the whole list — so reading the table names meant scrolling
   sideways through sixty rows. The name ellipsises and keeps its full text in the title. */
.pubtable > span:nth-child(2) { flex: 1 1 auto; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pubtable .pubsrid { margin-left: auto; flex: 0 0 auto;
  font: var(--t-tiny)/1 var(--mono); color: var(--faint); }
/* <b>Point, line or area, beside every table.</b> Owner instruction 2026-09-06. The same three
   shapes the contents tree draws for a layer's symbol, at the same size, so the pane and the
   tree read as one screen — but in the pane's own grey, because here the shape is the fact and
   a colour would imply a symbol nobody has chosen. A table with no geometry gets none of the
   three: it is not a feature class, and the row beneath it says so in words. */
.pubgeom { flex: none; width: 11px; height: 11px; }
.pubgeom.dot { width: 8px; height: 8px; border-radius: 50%;
  background: var(--accent); opacity: .75; }
.pubgeom.line { height: 0; width: 12px; border-top: 2px solid var(--accent); opacity: .75;
  transform: rotate(-20deg); }
.pubgeom.fill { border-radius: 2px; background: var(--accent-soft);
  border: 1.5px solid var(--accent); opacity: .8; }

/* Not publishable at all: the shape stays, so the row still says what the table holds, and the
   colour goes, so it does not read as an offer. */
.pubgeom.no.dot, .pubgeom.no.fill { background: var(--rule); border-color: var(--rule-strong); }
.pubgeom.no.line { border-top-color: var(--rule-strong); }
.pubwhy { padding: 0 8px 6px 54px; font-size: var(--t-tiny); color: var(--warn); line-height: 1.45; }


@media (max-width: 1180px) {
  .composer { grid-template-columns: 1fr; }
}
/* UX sweep 2026-10-19, findings 16 and 17. Stacked, each pane was a third of a fixed height — a 114 px scroller
   on a phone; it takes the height its contents need, the map a usable one. The map's zoom buttons and the
   map row's menu are 28 px targets rather than 22 and 17. */
@media (max-width: 1180px) {
  .composer { height: auto; min-height: 0; }
  .composer .composerbody { flex: none; max-height: 70vh; }
  #pubSummary .composerbody.pubcanvas { height: 320px; max-height: none; }
}
#pubMap .ol-zoom button { width: 28px; height: 28px; font-size: 18px; line-height: 1; }
.pubmenu { min-width: 28px; min-height: 28px; }
.pubopenwhy { align-self: center; color: var(--muted); font-size: var(--t-small); }
.pubopenwhy[hidden] { display: none; }

/* ADR-063 — the layer's Fields page. The label box fills its cell so a long label is readable
   while it is typed; the orphaned overrides are a plain list with their Remove beside them. */
#page-fields td input[type=text] { width: 100%; min-width: 8ch; text-overflow: ellipsis; }

/* Fixed widths for the three columns whose content is short, so Label keeps its share down to
   ~980px instead of being squeezed by a long column name — design review 2026-09-11. A long
   name wraps inside its cell rather than widening the table. */
#page-fields .fieldsgrid { table-layout: fixed; width: 100%; }
#page-fields .fieldsgrid col.c-name  { width: 19%; }
/* "SmallInteger" is the longest type name and does not wrap (below); 11% holds it at 1280. */
#page-fields .fieldsgrid col.c-type  { width: 11%; }
/* ADR-065: the Values button says what the column allows in a few words ("List of 12", or a date
   range, which wraps to two lines rather than being cut — design review 2026-09-12 measured every
   summary truncated at 1280 and the date range at 1440 when this was 13% and never wrapped). */
#page-fields .fieldsgrid col.c-values { width: 15%; }
/* ADR-064: what a column records about edits. A native select cuts its value with no ellipsis,
   so this is sized for the longest option, "Created on", at ~1100px — design review 2026-09-11,
   which found the first labels cut at 1440. Label keeps 24%. */
#page-fields .fieldsgrid col.c-records { width: 18%; }
#page-fields .fieldsgrid col.c-hide  { width: 14%; }
/* The label column keeps the rest, 23%. The name column went to 17% for a first draft of this and
   "material" broke mid-word at 1024 — the rule above is that only a long name may break. */
#page-fields .fieldsgrid select { width: 100%; }
/* Only a long column name may break; a type name broken mid-word ("Stri ng") reads as two. */
#page-fields .fieldsgrid td code { overflow-wrap: anywhere; }
#page-fields .fieldsgrid td:nth-child(2) { white-space: nowrap; }
/* The reason under a locked box: the page's own muted prose, not the monospace of a value. */
#page-fields .lockwhy { display: block; color: var(--muted); font-size: var(--t-small);
  line-height: 1.3; margin-top: 2px; }
#fieldsInert ul { list-style: none; padding: 0; margin: 6px 0 0; }
#fieldsInert li { display: flex; gap: 8px; align-items: baseline; padding: 4px 0; }

/* ADR-065 — values and subtypes on the Fields page. The editor and a subtype's detail are panels
   under what opened them, bounded in width so a two-column list does not stretch across a wide
   window; a Values button that has something set reads in ink rather than muted. */
#page-fields .valuesbtn { max-width: 100%; white-space: normal; text-align: left; overflow-wrap: anywhere;
  line-height: 1.3; }
/* The editor row spans the table and sits flush under the row that opened it. */
#page-fields tr.valuesrow > td { padding-top: 0; }
#page-fields .valuesedit .setting input { width: 180px; }
/* A name is prose: left-aligned, proportional, as wide as the panel allows — not the right-aligned
   monospace box `.setting` gives a number with a unit, which is what it inherited and why the
   review found "Pipe material" cut in a 116px box. */
#page-fields .valuesname { display: flex; flex-direction: column; gap: 4px; margin: 0 0 var(--gap-2);
  max-width: 420px; }
#page-fields .valuesname input { width: 100%; }
#page-fields .valuesnote { margin: var(--gap-2) 0 var(--gap-2); }
#page-fields .valuesbtn.set { color: var(--ink); }
#page-fields .valuesedit,
#page-fields .subtypedetail { border: 1px solid var(--rule); border-radius: var(--round);
  padding: var(--gap-3); margin: var(--gap-3) 0; max-width: 720px; background: var(--surface); }
#page-fields .valueskind { border: 0; padding: 0; margin: 0 0 var(--gap-2); display: flex;
  flex-wrap: wrap; gap: var(--gap-3); }
#page-fields .valueskind label { display: inline-flex; align-items: center; gap: 6px; }
#page-fields .valuesgrid,
#page-fields .subtypesgrid { width: 100%; max-width: 720px; }
#page-fields .valuesgrid input,
#page-fields .subtypesgrid input[type=text],
#page-fields .subtypesgrid input[type=number] { width: 100%; min-width: 6ch; }
#page-fields .subtypesgrid td.actions { white-space: nowrap; }
#page-fields .subtypesgrid tr.open td { background: var(--surface-2); }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ADR-066: the kind of source, chosen before any field, and the files a probe could not offer. */
#dbconn .dckind { border: 0; padding: 0; margin: 0 0 var(--gap-3); display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: var(--gap-2) var(--gap-4); }
#dbconn .dckind legend { font-weight: 600; margin-bottom: var(--gap-1); padding: 0; }
/* D-310's review: database servers and files as two stacked groups, each option on its own line. */
#dbconn .dckind .dcgroup { display: flex; flex-direction: column; gap: var(--gap-1); }
/* Body ink and weight: in the muted colour the heading read as one more disabled option (second review). */
#dbconn .dckind .dcgrouphead { font-weight: 600; }
#dbconn .dckind label { display: inline-flex; align-items: center; gap: 6px; min-height: 24px; }
#dbconn .dckind label:has(input:disabled) { color: var(--muted); }
#dbconn .dckind .hint { grid-column: 1 / -1; margin: 0; }
/* ADR-067: a checkbox sits beside its words, not under an uppercase caption like a text box. */
#dbconn label.field.check { flex-direction: row; align-items: center; gap: 6px; align-self: flex-end;
  padding-bottom: 8px; text-transform: none; letter-spacing: 0; font-size: var(--t-body); color: inherit;
  font-weight: 400; }
#dbconn #dcS3 { margin-top: var(--gap-2); }

/* ADR-067: an s3:// prefix is long and has no spaces, and it widened the Sources table past the page
   until it broke somewhere. Measured 2026-09-13 with Overture's prefix: 1,377 pixels in a 1,280 window. */
/* 2026-09-28: `anywhere` also shrank the column to one character, so a short path broke mid-token
   ("datastore:5432/gi" / "s"). The string now wraps only between the pieces breakAtSeparators()
   draws, each piece stays whole, and the full value is always on screen. */
#sources td.val .seg { white-space: nowrap; }
/* The four actions are one row, never a staircase. Remove stands a little apart because it is the
   one that cannot be undone. */
/* Left-aligned, so Probe, Pause, Edit and Remove stand in the same place on every row: right-aligned,
   the datastore's shorter set (two buttons and a note) pushed its Probe under the others' Pause.
   Pause and Resume share one width so the buttons after them do not move when a source is paused. */
#sources td.acts { white-space: nowrap; width: 1%; text-align: left; }
#sources td.acts [data-source-quiesce], #sources td.acts [data-source-resume] { min-width: 5.6em; }
#sources td.acts .tiny { padding: 3px 9px; }
#sources td.acts .danger { margin-left: var(--gap-3); }
#sources td.acts .managed { color: var(--faint); font-size: var(--t-small); }
#sources td.name .pill { margin-left: 6px; cursor: help; vertical-align: 1px; }
/* One row of four buttons makes the table about 720 pixels at its narrowest, and beside the
   register card that needs a 1,500 pixel window: measured at 1,280, Edit and Remove sat under the
   card. Below that the card goes under the table, and a phone scrolls the table sideways. */
@media (max-width: 1500px) { #view-sources .pickpair { grid-template-columns: minmax(0, 1fr); } }
#view-sources .pickpair > div > .panel { overflow-x: auto; }
/* UX sweep 2026-10-19, finding 12. The side card only explains now — Register is in the page head — so it is
   narrower and the table keeps its four buttons on one line. On a phone each source is a card: name and layer
   count, the connection under them, and the actions on a line of their own instead of past the right edge. */
@media (min-width: 1501px) { #view-sources .pickpair { grid-template-columns: minmax(0, 1fr) minmax(240px, 300px); } }
#sources .cardonly { display: none; }
@media (max-width: 600px) {
  #view-sources .panel table { min-width: 0; }
  #view-sources .panel thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  #sources tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px var(--gap-3);
    padding: var(--gap-3); border-bottom: 1px solid var(--rule); }
  #sources td { display: block; padding: 0; border: 0; }
  #sources td.num { grid-column: 2; grid-row: 1; color: var(--muted); white-space: nowrap; }
  #sources td.val { grid-column: 1 / -1; }
  #sources td.acts { grid-column: 1 / -1; width: auto; white-space: normal; display: flex; flex-wrap: wrap;
    gap: 6px; padding-top: 6px; }
  #sources td.acts .danger { margin-left: 0; }
  #sources td.acts .tiny { min-height: 36px; }
  #sources .cardonly { display: inline; }
}
#dbconn #dcS3 summary { cursor: pointer; }
.dcskipped { margin: var(--gap-2) 0 0; padding-left: 1.2em; }
.dcskipped li { margin: 2px 0; }

/* Server warnings (V-33): no caller and no address, so those two columns are not drawn, and the message
   is one line with the whole of it on hover and in the detail row. */
table.logs-server th:nth-child(3), table.logs-server th:nth-child(4),
table.logs-server tr.logrow td:nth-child(3), table.logs-server tr.logrow td:nth-child(4) { display: none; }
td.logmessage span[title] { display: inline-block; max-width: 60ch; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; vertical-align: bottom; }
/* Three columns, so no five-column minimum, and a long component name wraps inside its cell rather than past the
   card's edge (UX sweep 2026-10-19, finding 22). */
#view-logs .widetable table.logs-server { min-width: 0; }
table.logs-server td.logmessage span[title] { max-width: min(60ch, 52vw); }
table.logs-server tr.logrow td:last-child code { overflow-wrap: anywhere; white-space: normal; }
/* A phone: each label over its control, not a label left at the end of one line and its box on the next. */
@media (max-width: 600px) {
  #view-logs #logFilters { display: grid; grid-template-columns: minmax(0, 1fr); row-gap: 4px; }
  #view-logs #logFilters > label:not(:has(input[type=checkbox])) { margin-top: 6px; }
  #view-logs #logOwn { display: grid; gap: 4px; }
}

/*
  <b>The History page — ADR-078.</b> The changes, and under or beside them the chosen feature's versions.

  <b>Side by side only when the page itself is wide enough, measured on the page and not the window.</b>
  The first version switched at a 1100-pixel viewport; the review measured the page area at about 730
  pixels in a 1280 window, once the shell and the editor's own navigation are taken, and the list spilled
  under the versions and hid who made each change. A container query asks the page.
*/
#page-history { container-type: inline-size; }
.histsplit { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap-5);
             align-items: start; margin-top: var(--gap-3); }
@container (min-width: 1000px) {
  .histsplit { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); }
}
.histlist { overflow-x: auto; }
.histgrid td:first-child { white-space: nowrap; }
.histlegend { margin-top: var(--gap-2); }
.histfeature h5 { margin: 0 0 var(--gap-2); font-size: var(--t-body); }
.histversion { border: 1px solid var(--rule); border-radius: var(--round); padding: var(--gap-3);
               margin-bottom: var(--gap-2); background: var(--surface); }
.histversion .vhead { font-size: var(--t-small); display: flex; flex-wrap: wrap; gap: 2px var(--gap-3);
                      align-items: baseline; }
.histversion .vhead b, .histversion .vhead .until { white-space: nowrap; }
.histversion button { margin-top: var(--gap-2); }
.histattrs { font-size: var(--t-small); width: auto; }
.histattrs th { text-align: left; font-weight: 500; color: var(--muted); width: 1%; white-space: nowrap;
                padding: 2px var(--gap-3) 2px 0; vertical-align: top; text-transform: none;
                letter-spacing: normal; background: none; border-radius: 0; font-size: var(--t-small); }
.histattrs td { overflow-wrap: anywhere; padding: 2px 0; }
.histattrs del { color: var(--muted); }
#page-history .tag { display: inline-block; font-size: 11.5px; line-height: 1.5; padding: 0 6px;
                     border: 1px solid var(--rule-strong); border-radius: 999px; color: var(--muted);
                     vertical-align: 1px; white-space: nowrap; }

/* <b>The Settings screen's card — V-70.</b> A setting row stops at its own width, so a card as wide as
   the page left 424 pixels between a label and its box (design review 2026-09-24). */
.settingcard { max-width: 760px; }
/* Server › General's pointer to Studio's Security (ADR-189): a link, not a button, because nothing is set here. */
.settingcard .pointerlink { margin: var(--gap-3) 0 0; }
/* ADR-184 review: the gallery card's two lists one width, and never past the card on a phone. */
#basemapGallery { width: min(100%, 28rem); box-sizing: border-box; }
/* The Map page since the UX sweep of 2026-10-19: the default basemap as radio rows, the ground card's state as a
   pill beside its heading, and one Save bar under both cards that stays at the bottom of the window. */
#basemapDefault { margin-top: var(--gap-4); max-width: 36rem; }
#basemapDefault + .hint { margin-top: var(--gap-2); }
.cardhead { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2) var(--gap-3); }
.cardhead h4 { margin: 0; }
#groundInUse::before { display: none; }
#view-orgsettings .savebar { border: 1px solid var(--rule); border-radius: var(--round-lg); margin-top: var(--gap-4); }
#mapDirty:focus { outline: none; }

/* A label over its box at phone width, rather than a label broken over two lines beside it. */
@media (max-width: 560px) {
  .setting { flex-wrap: wrap; }
  .setting .q { flex-basis: 100%; }
}

/* ADR-093: the seed's area is a choice of words, not a number, so it is not held to the
   116-pixel numeric box beside it; at that width it read "The whole". */
#seedBox .setting select { width: auto; text-align: left; font-family: inherit; }

/*
  <b>An item page's own actions, 2026-09-30</b> — *Open in Map Viewer* and *Share*, on the right of the
  head as Portal's item page has them. The first is a link (it leaves for the viewer), dressed as the
  primary button it acts as; `.btn` already carries the shape.
*/
.itemhead .itemactions { margin-left: auto; display: flex; flex-direction: column; gap: var(--gap-2);
  align-items: stretch; flex: 0 0 auto; }
a.btn { display: inline-block; text-align: center; text-decoration: none; }
a.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600;
  box-shadow: var(--lift); }
a.btn.primary:hover { filter: brightness(.9); }
@media (max-width: 720px) {
  .itemhead { flex-wrap: wrap; }
  .itemhead .itemactions { margin-left: 0; flex-direction: row; }
}
.thumb.mapthumb { display: grid; place-items: center; }

/* The Caching page's area map and its Advanced section — 2026-09-30. */
.seedmap { height: 300px; border: 1px solid var(--rule); border-radius: var(--round); margin: var(--gap-2) 0 var(--gap-4); }
#page-caching .lede { max-width: 70ch; }
#page-caching details.advanced { margin-top: var(--gap-5); border-top: 1px solid var(--rule); padding-top: var(--gap-3); }
#page-caching details.advanced > summary { cursor: pointer; font-weight: 600; color: var(--ink); }
#page-caching .setting select { max-width: 100%; width: auto; min-width: 16rem; text-align: left; font-family: inherit; }

/* The Data tab's column headers are buttons that order by the column — 2026-09-30. */
button.thsort { all: unset; cursor: pointer; font: inherit; color: inherit; letter-spacing: inherit;
  text-transform: inherit; white-space: nowrap; }
button.thsort:hover { text-decoration: underline; }
button.thsort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.datatools { margin-bottom: var(--gap-2); }


/* The item's tab strip goes under its title when the two do not fit side by side, and scrolls rather
   than pushing the page sideways. */
@media (max-width: 1180px) {
  #servicePagehead { flex-direction: column; align-items: stretch; gap: var(--gap-3); }
  #serviceTabs { align-self: flex-start; max-width: 100%; overflow-x: auto; flex-wrap: nowrap; white-space: nowrap; }
}

/* The item's one Layer select, beside its tabs (ADR-102 step 1). */
#itemLayerField.itemlayer { margin: 0; flex: 0 1 280px; }
#itemLayerField.itemlayer select { width: 100%; }

/* Visualization's Style panel holds the layer's style editor (ADR-102). */
.stylehost { margin-top: var(--gap-3); container: symeditor / inline-size; }
.stylehost > #page-symbology { display: block; }
/* The item's head and the Visualization toolbar keep their margins while the editor spans the page. */
#app.symfull #view-service #editHead { display: flex; }
#app.symfull #view-service #editHead, #app.symfull #servicePagehead,
#app.symfull #serviceVis > .toolbar { padding-left: var(--gap-5); padding-right: var(--gap-5); }
#app.symfull #view-service #editHead { padding-top: var(--gap-4); }
/* While the editor is open it fills the window as it did on its own page; its strip carries the way back. */
#app.symfull #view-service #editHead, #app.symfull #servicePagehead, #app.symfull #serviceVis > .toolbar { display: none; }
#app.symfull .symstrip #visStyle { margin-right: var(--gap-3); }
#app.symfull .stylehost { margin-top: 0; }

/* Verification round 2 (ADR-102): the Layer select is a row of its own under the title, never a tall column;
   the item's actions wrap on a phone; Packages' selects read as the Pre-build ones do. */
#servicePagehead { flex-wrap: wrap; }
#itemLayerField.itemlayer { flex: 0 0 100%; order: 3; width: min(360px, 100%); max-width: 360px; }
@media (max-width: 1180px) { #itemLayerField.itemlayer { flex: none; width: min(360px, 100%); } }
.itemhead .itemactions { flex-wrap: wrap; min-width: 0; }
.itemhead .itemactions > * { flex: 1 1 auto; }
.itemhead > div { min-width: 0; }
#exportBox .setting select, #exportBox .setting input[type=number] { width: auto; min-width: 16rem; max-width: 100%;
  text-align: left; font-family: inherit; }
@media (max-width: 480px) { #serviceTabs a { min-height: 44px; display: inline-flex; align-items: center; } }
@media (max-width: 720px) { .itemhead .itemactions { flex: 1 1 100%; } }

/* Settings › Feature layer: the owner's choice of edits (ADR-102). */
fieldset.offered { border: 0; padding: 0; margin: 0; display: grid; gap: var(--gap-2); }
fieldset.offered legend { font-weight: 600; margin-bottom: var(--gap-2); }
fieldset.offered label.check { display: flex; gap: var(--gap-2); align-items: baseline; }

/* Overview's right column, as Portal's item page has it: what you can do, then the details (ADR-102 step 7). */
#serviceDetails .itemactions { display: flex; flex-direction: column; gap: var(--gap-2); margin: 0 0 var(--gap-5); }
#serviceDetails .itemactions > * { width: 100%; text-align: center; }
#serviceDetails .urlrow { display: flex; gap: var(--gap-2); }
#serviceDetails .urlrow input { flex: 1; min-width: 0; font-size: 12px; }
@media (max-width: 1100px) { #serviceDetails { order: -1; } }
#serviceDetails dl.facts2 dd { min-width: 0; }
#serviceDetails dl.facts2 dt { white-space: nowrap; }

/* Data › Fields: the layer's editor, then adding and removing columns (ADR-102 step 9). */
#dataRows details.columns { margin-top: var(--gap-5); }
#dataRows details.columns > summary { cursor: pointer; font-weight: 600; }
#dataFieldsHost > .page, #dataRows > .page { display: block; }
/* Settings › Feature layer: one block per layer. */
.layerblock { display: flex; align-items: center; gap: var(--gap-3); padding: var(--gap-2) 0; border-bottom: 1px solid var(--rule); }
.layerblock > button { margin-left: auto; }

/* ADR-102 step 10: the publisher's per-layer settings, now in the item. */
#featureLayers .layerblock { flex-wrap: wrap; }
#featureLayers .layerblock .setting { flex-basis: 100%; margin-top: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
/* Free text, not a number: the generic .setting rule above is 116px, right-aligned and monospace, which cut a
   title to its first word and pushed the placeholder to the far edge (design review, 2026-10-07). Both boxes one
   width so the two rows line up. */
#featureLayers .layerblock .setting input[data-time-input],
#featureLayers .layerblock .setting input[data-layer-title-input] {
  width: 24em; max-width: 100%; text-align: left; font-family: inherit; }
.symrail .visrange .setting { flex-wrap: wrap; gap: 4px; align-items: center; }
.symrail .visrange .setting .q { flex-basis: 100%; }
.symrail .visrange .row { flex-wrap: wrap; gap: 6px; }

/* Design review 2026-10-01 on steps 8-11. */
#itemThumb .thumbcol { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; flex: none; }
#itemThumb .thumbcol .hint { max-width: 168px; margin: 0; }
#exportData .stacked, #updateData .stacked { display: flex; flex-direction: column; gap: 4px; margin-bottom: var(--gap-3); }
#exportData .stacked select, #updateData .stacked select, #updateData .stacked input { font: inherit; max-width: 100%; }
#exportData fieldset + .hint, #updateData fieldset { margin-bottom: 12px; }
#exportData fieldset + .hint { margin-top: 12px; }
#exportData h3:focus:not(:focus-visible), #updateData h3:focus:not(:focus-visible) { outline: none; }
#featureLayers .layerblock .setting .q { width: auto; min-width: 0; }
#featureLayers input[data-time-input], #featureLayers input[data-layer-title-input] { font: inherit; }
#featureLayers button.ghost, .symrail .visrange button.ghost { border: 1px solid var(--control-edge); }
details.removelayer { margin-top: var(--gap-3); }
details.removelayer > summary { cursor: pointer; color: var(--alert); }
details.removelayer .row { display: flex; flex-wrap: wrap; gap: 6px; margin: var(--gap-2) 0; }

/* Server › service › Layers (owner decision 2026-10-01). */
#page-layers .srvlayer { padding: var(--gap-3) 0; border-bottom: 1px solid var(--rule); }
#page-layers .srvlayer .row { display: flex; align-items: center; gap: var(--gap-2); flex-wrap: wrap; }
/* The layer an address asked for: a full hairline and a tint, not a one-sided bar (UX sweep 2026-10-19, finding 21). */
#page-layers .srvlayer.asked { background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent-line);
  border-radius: var(--round); }
#page-layers .srvgroups { margin: var(--gap-3) 0 0; }
#page-layers .srvlayer { scroll-margin-top: 160px; }
#page-layers .srvlayer h4 { margin: 0; padding: 0; border: 0; text-transform: none; letter-spacing: 0;
  font-size: inherit; color: inherit; font-weight: 600; }
#page-layers .srvlayer { padding-left: 12px; padding-right: 12px; }
#page-layers .srvlayer dd .val { font-family: inherit; }
#page-layers .srvlayer:focus { outline: none; }
#updateData .check > span { display: flex; flex-direction: column; }
#updateData .check .hint { margin: 0; }
#updateData .dialogfoot .fill { flex: 1; margin: 0; }

/* The preview is drawn as a layer of the map; the <img> only holds it (owner 2026-10-01, lag on zoom). */
#symPreview.onmap { visibility: hidden; }

/* Settings › Feature layer › each layer: its name on a line, then one row per setting, labels in one column. */
#featureLayers .layerblock { display: block; }
#featureLayers .layerblock > b { display: inline-block; margin-right: 6px; }
#featureLayers .layerblock .setting { display: grid; grid-template-columns: 180px auto auto auto; justify-content: start;
  align-items: center; gap: 8px; margin: 8px 0 0; max-width: none; }
#featureLayers .layerblock .setting .q { width: auto; text-align: left; }
#featureLayers .layerblock .setting .val { font-family: inherit; }
/* The time box's width is set once, with the title's, above; a second rule here at 14em won by coming later and the
   two rows stopped lining up. And minmax(0, 1fr), not 1fr: a bare 1fr track cannot shrink below a 24em box, so at
   phone width the title row ran past the card (design review, 2026-10-07). */
/* A history's keeping period: the box and its unit are one grid cell, so the row has the four its neighbours have. */
#featureLayers .layerblock .setting .keepdays { display: inline-flex; align-items: center; gap: 6px; }
#featureLayers .layerblock .setting .keepdays .u { width: auto; }
/* One value, not a column of numbers: the neighbours' font and alignment, and their height (design review). */
#featureLayers .layerblock .setting .keepdays input { width: 6em; text-align: left; font: inherit; }
/* Out of the grid, under the box: inside it, spanning the row stretched Set and Keep all to the card's edge, and one
   column squeezed it to six lines (design review, 2026-10-07). 180px and the 8px gap are the label column's. */
#featureLayers .layerblock .keepwhy { margin: 4px 0 0 188px; max-width: 70ch; }
/* By the block's own width, not the window's — design review 2026-10-21: between 601 and about 1000 px the card is
   narrower than the four columns, #serviceEdit clips its overflow, and Set, Use the name and Derive it were cut off. */
#featureLayers { container-type: inline-size; }
/* The two places side by side, wrapping as one control (finding 7) — before the narrow rule, which overrides it. */
#featureLayers .layerblock .setting .attachchoice { display: inline-flex; flex-wrap: wrap; gap: 6px; }
#featureLayers .layerblock [data-steward-error] { margin: 6px 0 0 188px; max-width: 70ch; color: var(--alert); }
#featureLayers .layerblock [data-steward-error]:empty { display: none; }
@container (max-width: 640px) {
  #featureLayers .layerblock .keepwhy { margin-left: 0; }
  /* One column, controls at their own width: a mouse at 768 px gets no 600 px buttons (review round 2, R5). */
  #featureLayers .layerblock .setting { grid-template-columns: minmax(0, 1fr); justify-items: start; }
  #featureLayers .layerblock .setting .attachchoice { display: inline-grid; grid-template-columns: auto; }
  #featureLayers .layerblock [data-steward-error] { margin-left: 0; }
}
/* On a phone the controls stretch, as the rest of the console's do. */
@media (max-width: 600px) {
  #featureLayers .layerblock .setting { justify-items: stretch; }
  #featureLayers .layerblock .setting .attachchoice { display: grid; grid-template-columns: minmax(0, 1fr); }
}

/* A web map's item page (2026-10-01): the main column and the actions beside it, as the layer item. */
/* Since the UX sweep of 2026-10-19 it is the layer's shell: a head card with the picture, the name and the actions,
   the tabs under them, then the item's information beside its summary and layers. */
#mapPagehead {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas: "thumb text actions" "tabs tabs tabs";
  align-items: start; column-gap: 0; row-gap: var(--gap-4); margin-bottom: var(--gap-5);
  padding: var(--gap-4) var(--gap-5) 0; background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--round-lg); box-shadow: var(--lift);
}
/* The gaps ride on the picture and the actions, as the layer's head has them, so a tab without the picture does not
   keep an empty column's gap in front of the title (round 2: 23 pixels deeper than a layer's). */
#mapPagehead > #mapThumb { grid-area: thumb; margin-right: var(--gap-5); }
#mapPagehead:not([data-tab="overview"]) > #mapThumb { display: none; }
#mapPagehead > #mapThumb .thumb { width: 180px; height: 116px; max-width: none; border-radius: var(--round-lg); }
#mapPagehead > .herotext { grid-area: text; min-width: 0; align-self: center; }
#mapPagehead > .herotext h2 { margin: 0 0 4px; overflow-wrap: break-word; }
#mapPagehead > .herotext p { margin: 0; color: var(--muted); }
#mapPagehead > #mapActions {
  grid-area: actions; max-width: 440px; margin-left: var(--gap-5); display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--gap-2);
}
#mapActions > button { padding: 6px 12px; }
#mapPagehead > #mapTabs { grid-area: tabs; justify-self: start; align-self: end; }
@media (max-width: 900px) {
  #mapPagehead { grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "thumb text" "actions actions" "tabs tabs"; }
  #mapPagehead > #mapActions { max-width: none; justify-content: flex-start; margin-left: 0; }
  #mapPagehead > #mapThumb .thumb { width: 120px; height: 78px; }
}
@media (max-width: 480px) { #mapPagehead > #mapThumb .thumb { width: 88px; height: 58px; } }
#mapSettings h4:first-child { margin-top: 0; }
#mapSettings #mapDelete { margin-top: var(--gap-2); }
.maplayershead { margin: var(--gap-4) 0 var(--gap-1); font-size: var(--t-small); font-weight: 600; color: var(--muted); }
.mapitem { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: var(--gap-4); align-items: start; }
.mapitem .mapmain { display: flex; flex-direction: column; gap: var(--gap-4); min-width: 0; }
.mapitem h4:first-child { margin-top: 0; }
.maplayers { list-style: none; margin: 0; padding: 0; }
.maplayers li { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; padding: 8px 0; border-bottom: 1px solid var(--rule); }
.maplayers li:last-child { border-bottom: 0; }
#mapSide .itemactions { display: flex; flex-direction: column; gap: var(--gap-2); margin-bottom: var(--gap-3); }
#mapAbout .stacked, #mapShare .stacked { display: flex; flex-direction: column; gap: 4px; margin-bottom: var(--gap-3); }
#mapAbout textarea, #mapAbout input { font: inherit; max-width: 100%; }
#mapShare .check > span { display: inline-flex; flex-direction: column; }
/* One column on a narrow screen, with the actions first: stacked after the description and the layers, Open in Map
   Viewer was below the fold at 390 px (ADR-079 condition 2's review, 2026-10-14). */
@media (max-width: 900px) { .mapitem { grid-template-columns: minmax(0, 1fr); } }
#mapAbout, #mapLayers { padding: var(--gap-3) var(--gap-4); }

/* A button that reads as a link, beside a fact it changes (Change owner…). */
.linkbtn { background: none; border: 0; padding: 0; margin-left: 6px; color: var(--link); font: inherit; font-size: 13px;
  text-decoration: underline; cursor: pointer; }
#changeOwner .stacked { display: flex; flex-direction: column; gap: 4px; margin-bottom: var(--gap-3); }
#changeOwner select { font: inherit; max-width: 100%; }

/* Design review 2026-10-01, round 4. */
#mapSide { padding: 22px; }
.maplayers .kindtag { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted, #667);
  border: 1px solid var(--rule); border-radius: 4px; padding: 1px 6px; }
dialog h3[tabindex="-1"]:focus { outline: none; }
#exportData .formats .check > span { display: flex; flex-direction: column; }
#exportData .formats .hint { margin: 0; }
#exportData .dialogfoot .fill { flex: 1; margin: 0; }
/* At a phone's width the footer's message has its own line above the buttons, rather than a column 90px wide that cut
   the end of a refusal off (D-289's review). */
@media (max-width: 480px) {
  #exportData .dialogfoot { flex-wrap: wrap; }
  #exportData .dialogfoot .fill { flex-basis: 100%; }
}
#changeOwner select[size] { min-height: 8em; }
.linkbtn { min-height: 24px; }
@media (max-width: 600px) { .linkbtn { min-height: 44px; } }
#exportData .formats + .formats { margin-top: 14px; }

/* Update data's mapping step (2026-10-01). */
#updateDataMap { margin-bottom: var(--gap-3); }
#updateData .mapcols { width: 100%; border-collapse: collapse; }
#updateData .mapcols th, #updateData .mapcols td { text-align: left; padding: 4px 6px; border-bottom: 1px solid var(--rule); }
#updateData .mapcols select { font: inherit; max-width: 100%; }

/* ADR-111: an item's tags. */
.tagline { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; }
/* A service's credits read as a field, not a sentence, and a long one stops at two lines (ux review, 2026-10-13). */
.hint.credits { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.hint.credits .key { font-weight: 600; margin-right: 6px; color: var(--ink, inherit); }
.tagline .tag { font-size: 12px; padding: 2px 8px; border: 1px solid var(--rule); border-radius: 999px; color: var(--ink, inherit); }

/* ADR-113: the view dialog's layer rows, and the filters listed under View of. */
.cvlayer { display: grid; gap: 4px; margin-bottom: var(--gap-2, 8px); }
.cvlayer input[type=text] { width: 100%; }
ul.plain { list-style: none; margin: 4px 0 0; padding: 0; }

/* The view dialog's own layout (design review 2026-10-01): a label over its control, an option's name over its hint. */
#createView .stacked { display: flex; flex-direction: column; gap: 4px; margin-bottom: var(--gap-3, 12px); }
#createView .check > span { display: flex; flex-direction: column; }
#createView .check .hint, #createView .dialogfoot .fill { margin: 0; }
#createView .dialogfoot .fill { flex: 1; }
#createView input[type=text], #createView select { width: 100%; font: inherit; }
#createView fieldset + fieldset { margin-top: 14px; }
#createView .fieldchips .linkbtn { margin-right: 6px; }

/* ADR-114: the caller's folders under the content scopes. */
.folderbar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 8px 0; }
.folderbar .chip { font-size: 13px; padding: 6px 12px; border: 1px solid var(--rule); border-radius: 999px;
  background: transparent; color: inherit; cursor: pointer; min-height: 32px; }
.folderbar .chip.on { background: var(--accent-soft); border-color: var(--accent); font-weight: 600; }
.folderbar .chip .count { color: var(--muted); font-variant-numeric: tabular-nums; font-size: var(--t-small); margin-left: 4px; }
.folderbar .folderlabel { font-weight: 600; margin-right: 4px; }
.folderbar .foldersep { width: 1px; align-self: stretch; background: var(--rule); margin: 0 4px; }
.folderbar .foldermenu { display: inline-block; }
#contentFolderDialog .stacked { display: flex; flex-direction: column; gap: 4px; margin-bottom: 12px; }
#contentFolderDialog input[type=text], #contentFolderDialog select { width: 100%; font: inherit; }
#contentFolderDialog .dialogfoot .fill { flex: 1; margin: 0; }

/* ADR-115: the own-only box takes the row's width, its note indented under it (design review 2026-10-01). */
#featureLayers .setting.ownonly { flex-wrap: wrap; }
#featureLayers .setting.ownonly > label.check { flex: 1 1 100%; display: flex; gap: var(--gap-2, 8px); align-items: baseline; }
#featureLayers .setting.ownonly > .rowmeta { padding-left: 24px; }

/* ADR-116: at phone width the Update data answer takes its own line rather than a sliver beside the buttons. */
@media (max-width: 480px) { #updateDataFoot { flex-wrap: wrap; } #updateDataSays { flex-basis: 100%; } }

/* ADR-119: the web map item's picture beside its summary, and its description. */
.mapabout { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.mapabout .mapfigure { margin: 0; flex: 0 0 auto; width: 100%; max-width: 240px; }
.mapabout .mapitemthumb { width: 100%; max-width: 240px; aspect-ratio: 3 / 2; height: auto; object-fit: cover; display: block; }
.mapabout > div { flex: 1 1 240px; min-width: 0; }
.mapdescription { white-space: normal; line-height: 1.5; margin-bottom: 8px; }

/* ADR-123: an image service's Display settings beside a picture of the result. */
.coveragedisplay { display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-start; }
.coveragedisplay > div { flex: 1 1 260px; min-width: 0; }
.coveragedisplay .coveragefigure { margin: 0; flex: 0 1 360px; min-width: 0; }
.coveragedisplay .coveragepreview { display: block; width: 100%; max-width: 360px; height: auto; object-fit: contain;
  background: var(--surface-2); border: 1px solid var(--rule); border-radius: 6px; }
.coveragedisplay .ramplegend { height: 10px; max-width: 360px; margin-top: 6px; border-radius: 3px; border: 1px solid var(--rule); }
.coveragedisplay .covsave { margin-top: 16px; }
.coveragedisplay .field { display: block; margin-bottom: 16px; }
.coveragedisplay #covRange { gap: 16px; }
.coveragedisplay #covRange .field { margin-bottom: 0; }
.coveragedisplay .coveragepreview.stale { opacity: .4; }
.coveragedisplay #covRangeSays:empty { display: none; }
.rampchoice { display: flex; align-items: center; gap: 8px; }
.rampchoice select { flex: 1 1 auto; min-width: 0; }
.rampswatch { flex: 0 0 48px; height: 12px; border-radius: 3px; border: 1px solid var(--rule); }

/* A group's Content at phone width: kind and reach under the name rather than in columns scrolled out of view —
   from 720 down since the table became blocks there (sweep 2026-10-19). */
.narrowonly { display: none; }
h4.spaced { margin-top: 24px; }
@media (max-width: 719px) {
  #groupItems .widecol, #view-group th.widecol, th.widecol { display: none; }
  #groupItems .narrowonly { display: inline; }
  /* Two buttons do not fit beside a name at 390; they take their own line under it. */
  #groupMembers td.groupmemberacts { grid-column: 1 / -1; grid-row: auto; text-align: left; }
}

/* ADR-129: My content's selection — a tick column and the bar of what can be done to every ticked item. */
#view-content th.tick, #contentRows td.tick { width: 28px; text-align: center; }
.bulkbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 8px;
  padding: 8px 12px; border: 1px solid var(--rule); border-radius: 6px; background: var(--surface-2); }
.bulkbar[hidden] { display: none; }

#contentRows td.tick input, #contentPickAll { width: 20px; height: 20px; }
#contentRows td.tick { cursor: pointer; }
.bulkbar .bulksaid { flex: 1 0 100%; margin: 0; }
.sharewarn { color: var(--warn); background: var(--warn-soft); border: 1px solid var(--warn-line); border-radius: 6px;
  padding: 8px 10px; margin: 8px 0; }
.sharewarn > span[aria-hidden] { font-weight: 700; }
/* At phone width the picture gives its room to the name, which a bulk delete must be read by. */
@media (max-width: 480px) {
  #contentRows td.thumbcell { display: none; }
}

/* ADR-135: at phone width a My content row gives its width to the name and its count — the picture and the row's
   menu go (the item page and the selection bar keep every action), so the name and its usage can be read. */
@media (max-width: 480px) {
  #view-content .widetable th:last-child, #contentRows td:last-child { display: none; }
  #contentRows td.name { min-width: 0; width: auto; }
}

/* ADR-136: the legend's ends under a raster function's bar. */
.coveragedisplay .rampticks { display: flex; justify-content: space-between; max-width: 360px; font-size: 11px;
  color: var(--muted, #576a66); margin-top: 2px; }
.coveragedisplay .rampticks:empty { display: none; }
.coveragedisplay label.stacked { display: block; }
.coveragedisplay #covFunction { width: 100%; margin-bottom: 4px; }

/* ADR-139's review: the upload's bar was the browser's 150 px, which a four-gigabyte file barely moves; and the waiting
   line's clock keeps its width as it counts. */
#imgProgress { width: 100%; height: 8px; }
#imgProgressSays { font-variant-numeric: tabular-nums; }

/* ADR-140's review: a size beside body text in body type, its figures aligned rather than the monospace of .val. */
#imgChosen .num { font-variant-numeric: tabular-nums; }

/* ADR-143: the download's line takes no room until it has something to say; it is rendered from the start so a screen
   reader has the live region before its first message. */
#imageDownloadSays:empty { margin: 0; }

/* Settings › General › Download (ADR-148): the box in its own column, the text hanging beside it, a row tall enough to
   touch; and room before the danger panel below (the ux review, 2026-10-03). */
/* ADR-182: the elevation surface's box beside it, styled as one — it had none of this and its link touched the next
   heading (the ux review, 2026-10-13). */
#generalDownload, #generalElevation { margin-block: 20px 24px; }
#generalDownload .check-row, #generalElevation .check-row { display: grid; grid-template-columns: auto 1fr; gap: 8px; align-items: center; min-height: 32px; }
#generalDownload #imageDownloadHint, #generalElevation #imageElevationHint, #generalElevation #imageElevationSaid { padding-left: 28px; }
/* A long address wraps rather than being cut at the card's edge at 390 px. */
#geometryServiceSource, #geometryServiceSays { overflow-wrap: anywhere; }
/* Add images while it works: still focusable, plainly busy. */
#imageAdd[aria-disabled="true"] { color: var(--muted); border-style: dashed; cursor: progress; }
#imageAddProgress { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin: 4px 0 var(--gap-3); }
#imageAddBar { flex: 1 1 8rem; }
#imageAddShown { font-variant-numeric: tabular-nums; }
#generalDownload #imageDownloadSaid { padding-left: 28px; }
#imageAddProgress[hidden] { display: none; }

/* ADR-151, ux review 6: a band function's arguments, unboxed, labels above their selects. */
.coveragedisplay fieldset.row { border: 0; padding: 0; margin: 8px 0 4px; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.coveragedisplay fieldset.row .field { display: flex; flex-direction: column; gap: 3px; margin-bottom: 0; }
.coveragedisplay fieldset.row select { min-width: 96px; }
.coveragedisplay #covExpr { font-family: var(--mono, ui-monospace, monospace); }
.coveragedisplay #covFunctionError:empty { display: none; }
/* ADR-152: Settings › Images, a row an image; the table scrolls in its own box at phone width. */
#coverageImages .tablewrap { overflow-x: auto; max-width: 100%; }
#coverageImages .imageName { width: 100%; min-width: 10rem; }
#coverageImages td.num { font-variant-numeric: tabular-nums; }
/* ADR-154: Settings › Classes, a row a value. */
#coverageClasses .tablewrap { overflow-x: auto; max-width: 100%; }
#coverageClasses .className { width: 100%; min-width: 10rem; }
#coverageClasses input[type=color] { width: 3rem; height: 2rem; padding: 0; }
/* ux review 7: a table's screen-reader-only header stays inside its scroll box, so the page does not scroll sideways. */
.tablewrap { position: relative; }
#coverageImages td { padding-block: 6px; }
#coverageImages td.acts { white-space: nowrap; }
#coverageImages .imageFile { display: block; font-size: var(--t-small); overflow-wrap: anywhere; }
#coverageImages tr[data-dirty="true"] td:first-child { box-shadow: inset 3px 0 0 var(--accent, #0f6d5f); }
#coverageImages .field.inline { display: inline-flex; gap: 8px; align-items: center; margin: 0; }
/* ux review 8: Classes' actions above the table, Save kept in reach, rows the height of Images'. */
#coverageClasses td { padding-block: 6px; }
#coverageClasses .covsave { position: sticky; bottom: 0; background: var(--surface, #fff); padding-block: 8px; z-index: 1; }
#coverageClasses .colourpair { display: flex; gap: 6px; align-items: center; }
#coverageClasses input[type=color] { flex: none; }
#coverageClasses input[type=color][data-none="true"] { background: repeating-linear-gradient(45deg, var(--surface-2, #eee) 0 4px, transparent 4px 8px); }
#coverageClasses .classHex { width: 6.5rem; }
#coverageClasses .classValue { width: 4.5rem; }
@media (max-width: 480px) {
  #coverageClasses tr[data-row] { display: grid; grid-template-columns: 4.5rem 1fr; gap: 4px 8px; padding-block: 6px; border-bottom: 1px solid var(--rule, #e5e7eb); }
  #coverageClasses tr[data-row] td { padding: 0; border: 0; }
  #coverageClasses thead { display: none; }
}
/* ux review 9: Save stays in reach — the panel clips without becoming the sticky element's scroll box. */
#serviceEdit { overflow: clip; }
.classkey { display: inline-flex; align-items: center; gap: 4px; margin-right: 8px; }
.classkey i { display: inline-block; width: 12px; height: 12px; border: 1px solid var(--rule, #ccc); border-radius: 2px; }
#coverageImages .covsave { position: sticky; bottom: 0; background: var(--surface, #fff); padding-block: 8px; z-index: 1; }
/* ux review 11: a slice's values on one line, and the name wide enough to tell rows apart. */
#coverageImages td.imageSlice { white-space: nowrap; }
#coverageImages .imageName { min-width: 8rem; }
#coverageImages .imageTaken { width: 9.5rem; }
#coverageImages td.acts .tiny { padding-inline: 6px; }
/* ux review 11: a multidimensional table has a column more, so Remove goes under Raise and Lower. */
#coverageImages table.sliced td.acts .danger { display: block; margin-top: 8px; }
#coverageImages table.sliced .imageTaken { width: 8.5rem; }
@media (max-width: 480px) {
  #coverageClasses tr[data-row] { grid-template-columns: 4.5rem 1fr auto; }
  #coverageClasses tr[data-row] td:nth-child(2) { grid-column: 2 / 4; }
  #coverageClasses tr[data-row] td:nth-child(3) { grid-column: 1 / 3; }
  #coverageClasses input, #coverageClasses button, #coverageImages button { min-height: 44px; }
  #coverageImages thead { display: none; }
  /* ux review 11: a cell a line, each labelled, the ID and the stack side by side. */
  #coverageImages tr[data-image] { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 8px; padding-block: 6px; border-bottom: 1px solid var(--rule, #e5e7eb); }
  #coverageImages tr[data-image] td { padding: 0; border: 0; grid-column: 1 / -1; text-align: start; }
  #coverageImages tr[data-image] td.num:not(.imageSlice) { grid-column: auto; }
  #coverageImages tr[data-image] td[data-label]::before { content: attr(data-label) " "; font-weight: 600; font-size: var(--t-small); }
  #coverageImages tr[data-image] td[data-label] .imageTaken { display: block; margin-top: 2px; }
}
/* ux review 10: a class without a colour is checkered, not white. */
#coverageClasses input[type=color][data-none="true"]::-webkit-color-swatch { background: repeating-conic-gradient(#c8ced6 0 25%, #fff 0 50%) 0 0 / 8px 8px !important; }
#coverageClasses input[type=color][data-none="true"]::-moz-color-swatch { background: repeating-conic-gradient(#c8ced6 0 25%, #fff 0 50%) 0 0 / 8px 8px !important; }

/* ADR-166: General › OGC services, one switch a face, its address beside it. */
#generalOgc { margin-bottom: 16px; }
#generalOgc .ogcterms { margin-top: 24px; max-width: 40rem; }
#generalOgc .ogcterms fieldset { border: 0; padding: 0; margin: 0; display: grid; gap: 8px; }
#generalOgc .ogcterms textarea { width: 100%; resize: vertical; }
#generalOgc .ogcterms .field { display: grid; gap: 2px; }
#generalOgc .ogcterms .field .hint { margin: 0; text-transform: none; letter-spacing: normal; font-weight: 400; }
#generalOgc #inspireLanguage { justify-self: start; inline-size: auto; max-inline-size: 100%; }
#generalOgc .inspire-state { margin: 0; font-size: var(--t-small); font-weight: 400; }
#generalOgc .inspire-state.warn { font-weight: 600; }
#generalOgc #ogcInspire fieldset { gap: 14px; }
#generalOgc .ogcterms [aria-disabled="true"] { opacity: .6; cursor: progress; }
#generalOgc .ogcfaces { border: 0; padding: 0; margin: 0; display: grid; gap: 6px; }
#generalOgc legend { padding: 0; }
#generalOgc legend h4 { margin: 0; }
#generalOgc .check-row { display: grid; grid-template-columns: 13rem 1fr; align-items: center; gap: 4px 12px; min-height: 36px; }
#generalOgc .check-row > * { min-width: 0; }
#generalOgc input[aria-disabled="true"], #generalOgc input[aria-disabled="true"] + span { opacity: .5; }
@media (max-width: 480px) { #generalOgc .check-row { grid-template-columns: 1fr; } }

/*
  <b>Below 1,100 pixels the section bar takes a row of its own, and a page's left column goes above the
  page — ADR-187.</b> The design review measured where one row stops holding the bar (Studio's five words
  beside the switch and the name) and where a 200-pixel page column starts hiding a table's actions
  (Members needs 823 pixels); 1,100 is both, and it is the breakpoint the console's paired panels
  already use. The bar scrolls inside itself and fades at its end, so a word past the edge is seen to be
  there.
*/
@media (max-width: 1100px) {
  header:not(.dialoghead) { height: auto; min-height: 52px; flex-wrap: wrap; row-gap: 0; padding-top: var(--gap-2); }
  header .right { margin-left: auto; }
  #tabs { order: 4; flex: 1 1 100%; margin-left: 0; overflow-x: auto; scrollbar-width: none;
          border-top: 1px solid var(--rule); min-height: 44px; scroll-padding-inline: 12px;
          -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
          mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); }
  .view.withside.on { display: block; }
  .view.withside > nav.subpages { position: static; flex-direction: row; overflow-x: auto;
                                  margin-bottom: var(--gap-4); border-bottom: 1px solid var(--rule); }
  .view.withside > nav.subpages a { white-space: nowrap; border-left: 0; border-bottom: 2px solid transparent;
                                    border-radius: 0; }
  .view.withside > nav.subpages a[aria-current="page"] { border-bottom-color: var(--accent); background: none; }

  /* The bar has its own row now, so the first row has room for the health sentence again, cut short. */
  :root[data-surface] #healthLine { display: block; flex: 1 1 0; }

  /* <b>A wide table's actions stay on screen</b> — the phone rule below, from 1,100 down: Members needs 823
     pixels and its row actions were past the edge at 768 (design review re-measure). */
  .widetable table td:last-child, .widetable table th:last-child {
    position: sticky; right: 0; z-index: 1; background: var(--surface);
    box-shadow: -10px 0 10px -10px rgba(16, 24, 38, .25);
  }
  .widetable table td:last-child:has(.menu[open]) { z-index: 3; }
  tr.pick:hover td:last-child { background: var(--surface-2); }
  /* The pinned header cell is the header's grey, not the body's white (design review, third pass). */
  .widetable table th:last-child { background: var(--surface-2); }
}
/* A third header row costs a phone more than the sentence is worth; the dot keeps it as its label. */
@media (max-width: 760px) { :root[data-surface] #healthLine { display: none; } }

/* ═══════════════════════════════════════════ ADR-187 step 2: Studio's My content and item page */

/*
  <b>My content's folders are a column on the left, the way Portal lists its folders beside the
  results.</b> Only while the folder list is there (Everything and Mine — other scopes have no folders of
  yours), which `:has` reads from the list's own `hidden`, so the router does not have to know. The head,
  the scope strip and everything under them keep their order; the column is the one thing placed.
*/
#contentSide > #contentFolders .folderlabel {
  font-size: 14px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--ink); margin: 0 0 6px 12px;
}
/* The chosen folder is told by its fill and weight, not by a border on one side (round 2). */
#contentSide > #contentFolders .chip {
  display: flex; justify-content: space-between; align-items: center; gap: var(--gap-2);
  text-align: left; border: 0; border-radius: var(--round);
  background: none; padding: 7px 12px; margin: 0 var(--gap-2); font-size: var(--t-body); color: var(--ink);
}
#contentSide > #contentFolders .chip:hover { background: var(--surface-2); }
#contentSide > #contentFolders .chip.on {
  background: var(--accent-soft); color: var(--accent-ink); font-weight: 600;
}
#contentSide > #contentFolders .foldersep { display: none; }
#contentSide > #contentFolders #folderNew,
#contentSide > #contentFolders .foldermenu { align-self: flex-start; margin: 6px 0 0 12px; }
#contentSide > #contentFolders .hint { margin-left: 12px; }

/*
  <b>The item's tabs are words with an underline under its title — Portal's item page.</b> They were a dark
  segmented control beside the title; Overview · Data · Visualization · Settings now run along the line
  that separates the item's head from its content, and the Layer select sits at the end of that line,
  where it applies to the tab beside it.
*/
#servicePagehead {
  flex-wrap: wrap; align-items: flex-end; column-gap: var(--gap-4); row-gap: var(--gap-3);
  border-bottom: 1px solid var(--rule); padding-bottom: 0;
}
#servicePagehead > .herotext { flex: 1 1 100%; }
#serviceTabs.segmented, #mapTabs.segmented {
  order: 2; align-self: flex-end; background: none; border: 0; padding: 0; gap: 0;
  border-radius: 0; margin: 0;
}
#serviceTabs.segmented a, #mapTabs.segmented a {
  padding: 10px 16px; margin-bottom: -1px; border-radius: 0; background: none; box-shadow: none;
  border-bottom: 2px solid transparent; color: var(--muted); font-size: var(--t-body); font-weight: 500;
}
#serviceTabs.segmented a:hover, #mapTabs.segmented a:hover { color: var(--ink); }
#serviceTabs.segmented a[aria-current], #mapTabs.segmented a[aria-current] {
  background: none; box-shadow: none; color: var(--link); border-bottom-color: var(--accent); font-weight: 600;
}
/* A resting tab reads as unavailable, not as a quieter tab: half its ink and a dotted underline (round 2). */
#serviceTabs.segmented a[aria-disabled="true"] {
  color: var(--muted); opacity: .5; cursor: not-allowed; text-decoration: underline dotted; text-underline-offset: 4px;
}
#serviceTabs.segmented a[aria-disabled="true"]:hover { color: var(--muted); }

/* The item's state, when it changes what the page can show: neutral, full border, one sentence and the way on. */
.itemnotice {
  margin: var(--gap-4) 0; padding: var(--gap-3) var(--gap-4); border: 1px solid var(--rule-strong);
  border-radius: var(--round-lg); background: var(--surface-2); color: var(--ink); font-size: var(--t-small);
  line-height: 1.5;
}
.itemnotice a { margin-left: var(--gap-2); font-weight: 600; }
#servicePagehead #itemLayerField.itemlayer {
  order: 3; flex: 0 1 280px; width: auto; max-width: 280px; margin: 0 0 8px auto;
  display: flex; align-items: center; gap: var(--gap-2);
}

@media (max-width: 1100px) {
  #contentSide > #contentFolders {
    position: static; flex-direction: row; flex-wrap: wrap; align-items: center; margin: 8px 0;
  }
  #contentSide > #contentFolders .chip {
    border: 1px solid var(--rule); border-radius: 999px; padding: 6px 12px; font-size: 13px; margin: 0;
  }
  #contentSide > #contentFolders .chip.on { border-color: var(--accent); }
  #serviceTabs.segmented { max-width: 100%; overflow-x: auto; flex-wrap: nowrap; }
  #servicePagehead #itemLayerField.itemlayer { flex: 1 1 100%; max-width: 360px; margin: 0 0 8px; }
}

/* ═══════════════════════════════════════════ ADR-187 step 3: Server's Services column */
.sidelist { display: flex; flex-direction: column; gap: 2px; margin: 0 0 var(--gap-4); }
.sidelist a {
  padding: 8px 12px; border-left: 3px solid transparent; border-radius: 0 var(--round) var(--round) 0;
  color: var(--muted); font-size: var(--t-body); text-decoration: none;
}
.sidelist a:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.sidelist a[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent-ink); border-left-color: var(--accent); font-weight: 600;
}
@media (max-width: 1100px) {
  .sidelist { flex-direction: row; border-bottom: 1px solid var(--rule); overflow-x: auto; }
  .sidelist a { border-left: 0; border-bottom: 2px solid transparent; border-radius: 0; white-space: nowrap; }
  .sidelist a[aria-current="page"] { background: none; border-bottom-color: var(--accent); }
}

/* ── Steps 2–3, after the design review's measurements (2026-10-16) ── */

/* The item head: its own ≤1180 rule stacks it as a column, where `flex-end` meant the right edge. */
@media (max-width: 1180px) {
  #servicePagehead { align-items: stretch; }
  #serviceTabs.segmented, #servicePagehead #itemLayerField.itemlayer { align-self: flex-start; }
  #serviceTabs.segmented { max-width: 100%; overflow-x: auto; overflow-y: hidden; flex-wrap: nowrap; padding-bottom: 1px; }
  #serviceTabs.segmented a { margin-bottom: 0; }
}
/* The Layer field is a row, label beside its select, so the tab line does not drop on Data. */
#servicePagehead #itemLayerField.itemlayer { flex-direction: row; align-items: center; gap: 8px; }
#serviceTabs.segmented a:focus-visible, #mapTabs.segmented a:focus-visible { outline-offset: -2px; }
/* On a phone the four tabs fit, and wrap rather than scroll out of sight: Settings, which holds sharing, the tile cache
   and Delete, sat at 313–400 in a 320 box with no scrollbar to say it was there (UX sweep 2026-10-19). */
@media (max-width: 600px) {
  #serviceTabs.segmented, #mapTabs.segmented { flex-wrap: wrap; overflow-x: visible; white-space: normal; }
  #serviceTabs.segmented a, #mapTabs.segmented a { padding: 12px 6px; font-size: 14px; }
  #serviceTabs.segmented a + a, #mapTabs.segmented a + a { margin-left: var(--gap-3); }
}

/* Server's General page: an address wraps rather than running out of its panel. */
#page-about dd code { overflow-wrap: anywhere; white-space: normal; }

/* The folder column scrolls inside itself when it is longer than the window, so New folder stays reachable. */
#contentSide > #contentFolders {
  max-height: calc(100vh - 112px); overflow-y: auto;
}
/* <b>A list, one folder a line, beside the results</b> (round 2): the shared `.folderbar` is a wrapping row, so with
   one folder All sat on the label's line, the folders shared a row and New folder floated under them. Chips are for
   the narrow layout, where the column lies above the list. */
@media (min-width: 1101px) {
  #contentSide > #contentFolders:has(.chip) { flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 2px; }
}
#contentSide > #contentFolders .folderlabel,
#contentSide > #contentFolders .hint,
#contentSide > #contentFolders .foldermenu,
#contentSide > #contentFolders #folderNew { margin-left: 15px; }
#contentSide > #contentFolders #folderNew { padding-left: 0; }
/* The menu acts on the selected folder above it; it says so instead of being a bare ⋯ (aria-label already does). */
#contentSide > #contentFolders .foldermenu summary::after {
  content: " Rename or delete…"; font-size: var(--t-small); color: var(--muted);
}

/* My content's toolbar wraps beside the folder column instead of squeezing the search box. */
#view-content > .toolbar { flex-wrap: wrap; row-gap: 8px; }
#view-content > .toolbar input[type="search"] { flex: 1 1 16rem; }
#view-content .toolbar label.check { white-space: nowrap; }
/* The count is a sentence and may take two lines — `nowrap` on it pushed a phone 225 pixels sideways. */
#contentCount { white-space: normal; overflow-wrap: anywhere; }

/* Services' list marks its page by the bar and weight, not the fill the selected folder under it uses. */
.sidelist a[aria-current="page"] { background: none; }

/* The item head stays a wrapping row at ≤1,180 rather than the column its older rule made it: the title takes
   the first line, the tabs and the Layer field share the second, and the tabs' underline lands on the head's
   rule (third design pass, 2026-10-16). */
@media (max-width: 1180px) {
  #servicePagehead { flex-direction: row; flex-wrap: wrap; align-items: flex-end; }
  #servicePagehead > .herotext { flex: 1 1 100%; }
  #serviceTabs.segmented { flex: 0 1 auto; min-width: 0; margin-bottom: -1px; padding-bottom: 0; }
  #servicePagehead #itemLayerField.itemlayer { flex: none; align-self: auto; margin: 0 0 8px auto; max-width: 360px; }
}
/* On a phone the Layer field cannot share the tabs' line; it goes above them so the tabs stay on the rule. */
@media (max-width: 600px) {
  #servicePagehead #itemLayerField.itemlayer { order: 1; margin: 0 0 8px; }
  #serviceTabs.segmented { order: 2; }
}

/* ═══════════════════════════════════════════ ADR-187 step 4: the dark theme */

/*
  <b>Dark is a choice the reader makes, and it is remembered.</b> The header's theme button sets
  `data-theme` on the root and keeps it in `localStorage`; `session.js` puts it back before the first
  paint. It is not taken from the operating system's preference, and since the 2026-10-19 sweep the map
  pages (Map Viewer, the layer viewer, the SDK viewer) read the same choice through `session.js` and copy
  these tokens, so a reader who chose dark gets a dark map page too. Every value below is a token the
  rest of the stylesheet already reads, so the theme is this block and not a second stylesheet.

  <b>Contrast is measured, not hoped.</b> Ink #e6ebf4 on paper #0a1120 is 15.8:1, muted #9aa7bd on the
  surface is 7.3:1, `--faint` #8592aa is 4.7:1 on `--surface-3`, link blue #6aa6ff is 6.0:1 on the darkest
  ground, and the primary button's white label on #2563eb is 5.2:1.
*/
:root[data-theme="dark"] {
  color-scheme: dark;
  --paper: #0a1120;
  --surface: #0f1828;
  --surface-2: #142034;
  --surface-3: #1b2840;
  --ink: #e6ebf4;
  --muted: #9aa7bd;
  --faint: #8592aa;
  --rule: #1f2b40;
  --rule-strong: #2b3a55;
  --control-edge: #62739a;
  --accent: #2563eb;
  --accent-hot: #3b76f0;
  --accent-soft: #11264a;
  --accent-ink: #6aa6ff;
  --link: var(--accent-ink);
  --accent-line: #24497f;
  --blue: #8fa8f5;
  --blue-soft: #1a2547;
  --blue-line: #34477e;
  --violet: #b39cfb;
  --violet-soft: #251c45;
  --violet-line: #463a77;
  --ok: #5fd39a;
  --ok-soft: #10301f;
  --ok-line: #1f5236;
  --warn: #f0bd5c;
  --warn-soft: #33260c;
  --warn-line: #5a4416;
  --alert: #f58a7f;
  --alert-soft: #3a1714;
  --alert-line: #6a2a24;
  --lift: 0 1px 2px rgba(0, 0, 0, .4);
  --lift-2: 0 6px 16px -6px rgba(0, 0, 0, .6);
}

:root[data-theme="dark"] button.primary:hover { filter: brightness(1.08); }

/* The mark is navy on a navy-dark header; a hairline keeps its square. */
:root[data-theme="dark"] #brand .mark,
:root[data-theme="dark"] #signin .signin-head .mark { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .22); }
:root[data-theme="dark"] #brand .badge,
:root[data-theme="dark"] #surfaces a[data-surface="server"][aria-current] { color: var(--accent-ink); }
:root[data-theme="dark"] #surfaces a[data-surface="server"][aria-current] { background: var(--accent-soft); }
:root[data-theme="dark"] #surfaces a[data-surface="studio"][aria-current] { background: var(--accent-soft); color: var(--accent-ink); }

/* The few places that wrote a light colour instead of reading a token. */
:root[data-theme="dark"] tr.pick:hover td,
:root[data-theme="dark"] tr.pick:hover td:last-child { background: var(--surface-2); }
:root[data-theme="dark"] .symstack,
:root[data-theme="dark"] .insppane .docpane textarea,
:root[data-theme="dark"] .styleoverride { background: var(--surface-2); }
:root[data-theme="dark"] .insppane .dochead .tag,
:root[data-theme="dark"] .thumb.empty.drewnothing b,
:root[data-theme="dark"] .symfoldhead,
:root[data-theme="dark"] .symoverride > b { color: var(--ink); }
:root[data-theme="dark"] .widetable table td:last-child { background: var(--surface); }
:root[data-theme="dark"] .widetable table th:last-child { background: var(--surface-2); }

/* Thumbnails and map pictures are drawn by the server on a light ground; a frame keeps them from glaring. */
:root[data-theme="dark"] .thumb,
:root[data-theme="dark"] .sympreview { border-color: var(--rule-strong); }

/* The theme button in the header: a word, so its state is not an icon to decode. */
#themeToggle { white-space: nowrap; }

/* Logs: which log is read is a column on the page's left, as Server Manager lists its log views (ADR-187 step 4). */
#view-logs.on { display: grid; grid-template-columns: 200px minmax(0, 1fr); column-gap: var(--gap-6); align-items: start; }
#view-logs.on > * { grid-column: 2; min-width: 0; }
#view-logs.on > .pagehead { grid-column: 2; }
#view-logs.on > #logSources {
  grid-column: 1; grid-row: 1 / span 12; position: sticky; top: 96px;
  display: flex; flex-direction: column; gap: 2px;
}
#view-logs > #logSources button {
  text-align: left; border: 0; border-left: 3px solid transparent; border-radius: 0 var(--round) var(--round) 0;
  background: none; color: var(--muted); padding: 8px 12px; font-size: var(--t-body); font-weight: 500; box-shadow: none;
}
#view-logs > #logSources button:hover { background: var(--surface-2); color: var(--ink); }
#view-logs > #logSources button[aria-selected="true"] {
  background: var(--accent-soft); color: var(--accent-ink); border-left-color: var(--accent); font-weight: 600;
}
@media (max-width: 1100px) {
  #view-logs.on { display: block; }
  #view-logs > #logSources { flex-direction: row; overflow-x: auto; border-bottom: 1px solid var(--rule); margin-bottom: var(--gap-3); }
  #view-logs > #logSources button { border-left: 0; border-bottom: 2px solid transparent; border-radius: 0; white-space: nowrap; }
  #view-logs > #logSources button[aria-selected="true"] { background: none; border-bottom-color: var(--accent); }
}

/* Dialogs read the tokens, so they follow the theme instead of keeping the browser's white. */
dialog {
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--rule); border-radius: var(--round-lg);
  box-shadow: var(--lift-2);
}
dialog::backdrop { background: rgba(16, 24, 38, .42); }
:root[data-theme="dark"] dialog::backdrop { background: rgba(0, 0, 0, .6); }

/* Surfaces that set their label in the paper colour on an ink ground, inverted for dark. */
:root[data-theme="dark"] #toast,
:root[data-theme="dark"] .insptabs .badge { color: var(--paper); }
:root[data-theme="dark"] td,
:root[data-theme="dark"] .setting.symclass { border-bottom-color: var(--rule); }

/* The "nothing drawn" hatches were light greys; in dark they are drawn from the surfaces. */
:root[data-theme="dark"] .thumb.empty,
:root[data-theme="dark"] .symempty .sw,
:root[data-theme="dark"] .sympreview,
:root[data-theme="dark"] .sympreview.ground-none {
  background: repeating-linear-gradient(45deg, var(--surface-2) 0 6px, var(--surface-3) 6px 12px);
}
:root[data-theme="dark"] .geoswatch {
  background: repeating-linear-gradient(45deg, var(--surface-2) 0 5px, var(--surface-3) 5px 10px);
}
:root[data-theme="dark"] ::placeholder { color: var(--faint); opacity: 1; }
:root[data-theme="dark"] .avatar {
  background: color-mix(in oklab, var(--avatar, var(--surface-3)) 22%, var(--surface-2));
  box-shadow: inset 0 0 0 1px var(--rule-strong); color: var(--ink);
}
:root[data-theme="dark"] .segmented.solid a[aria-current] {
  background: var(--surface-3); color: var(--ink); box-shadow: inset 0 0 0 1px var(--rule-strong);
}

/* Logs' sources lie along the top on a narrow window, as every other page's left column does. */
@media (max-width: 1100px) {
  #view-logs.on > #logSources { flex-direction: row; position: static; grid-row: auto; }
  #view-logs > #logSources button { flex: none; text-align: center; }
}

:root[data-theme="dark"] #addItem::backdrop,
:root[data-theme="dark"] #issued::backdrop { background: rgba(0, 0, 0, .6); }

/* ═══════════════════════════════════════════ My content's left column (owner's reference, 2026-10-16) */

/*
  <b>Portal's My content: a column of how an item reached you, your folders and the types present, and the
  list beside it.</b> The page head is in the list's column, as the reference draws it, so the column runs from
  the top of the page.
*/
#view-content.on {
  display: grid; grid-template-columns: 236px minmax(0, 1fr);
  column-gap: var(--gap-6); align-items: start;
}
#view-content.on > * { grid-column: 2; min-width: 0; }
#view-content.on > #contentSide {
  grid-column: 1; grid-row: 1 / span 20;
  position: sticky; top: 96px; max-height: calc(100vh - 112px); overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--gap-4);
  padding: var(--gap-3) 0; border: 1px solid var(--rule); border-radius: var(--round-lg);
  background: var(--surface);
}
#contentSide > .sidelist { margin: 0; }
#contentSide > .sidelist a { display: flex; justify-content: space-between; align-items: baseline; gap: var(--gap-2); }
#contentSide > .sidelist a .count { color: var(--muted); font-variant-numeric: tabular-nums; font-size: var(--t-small); }
#contentSide > .sidelist a[aria-current="page"] { background: var(--accent-soft); }
#contentSide > .sidelist a[aria-current="page"] .count { color: var(--accent-ink); }
#contentSide > #contentFolders { margin: 0; padding-top: var(--gap-3); border-top: 1px solid var(--rule); }

.facets { display: flex; flex-direction: column; gap: 2px; padding: var(--gap-3) 0 0; border-top: 1px solid var(--rule); }
.facets .folderlabel {
  font-size: 14px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--ink); margin: 0 0 6px 15px;
}
.facets label.check {
  display: flex; align-items: center; gap: var(--gap-2); padding: 5px 12px 5px 15px;
  font-size: var(--t-body); color: var(--ink); cursor: pointer;
}
.facets label.check:hover { background: var(--surface-2); }
.facets label.check > span:first-of-type { flex: 1; }
.facets .count { color: var(--muted); font-variant-numeric: tabular-nums; font-size: var(--t-small); }
.facets .linkbtn { align-self: flex-start; margin: 4px 0 0 15px; }

/* The type is a column now; it is short, so it does not wrap. */
#contentRows td.type { white-space: nowrap; }

@media (max-width: 1100px) {
  #view-content.on { display: block; }
  #view-content.on > #contentSide {
    position: static; max-height: none; overflow: visible; margin-bottom: var(--gap-4);
    border: 0; padding: 0; background: none; gap: var(--gap-2);
  }
  #contentSide > #contentFolders {
    flex-direction: row; flex-wrap: wrap; align-items: center; border-top: 0; padding-top: 0;
  }
  #contentSide > #contentFolders .chip {
    border: 1px solid var(--rule); border-radius: 999px; padding: 6px 12px; font-size: 13px;
  }
  #contentSide > #contentFolders .chip.on { border-color: var(--accent); }
  .facets { flex-direction: row; flex-wrap: wrap; align-items: center; border-top: 0; padding: 0; gap: 4px 6px; }
  .facets .folderlabel { margin: 0 4px 0 0; }
  .facets label.check { padding: 4px 10px; border: 1px solid var(--rule); border-radius: 999px; font-size: 13px; }
  .facets .linkbtn { margin: 0; }

  /* The scopes wrap as chips, as the folders and the types beside them do. As a scrolling strip, Organization began
     exactly at the edge of a phone's screen, with no fade or scrollbar to say there was more (UX sweep 2026-10-19). */
  #contentSide > .sidelist { flex-wrap: wrap; overflow-x: visible; border-bottom: 0; gap: 6px; }
  #contentSide > .sidelist a {
    padding: 6px 12px; border: 1px solid var(--rule); border-radius: 999px; font-size: 13px; white-space: nowrap;
  }
  #contentSide > .sidelist a[aria-current="page"] { border-color: var(--accent); }
}


/* ═══════════════════════════════════════════ The item's head card and Overview's three cards (owner's reference, 2026-10-16) */

/*
  <b>Picture, name and actions on one band, the tabs under it.</b> The reference is Portal's item page: the
  thumbnail on the left, the name and what it is beside it, and the actions on the right with the main one
  first and widest. Overview then reads as three cards — what the item is, what it says about itself, and the
  layers in it — where it was a wide column and a narrow one.
*/
#view-service #servicePagehead {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto;
  grid-template-areas: "thumb text actions actions" "tabs tabs tabs layer";
  align-items: start; column-gap: 0; row-gap: var(--gap-4);
}
#servicePagehead > #itemThumb { grid-area: thumb; margin-right: var(--gap-5); }
#servicePagehead:not([data-tab="overview"]) > #itemThumb { display: none; }
#servicePagehead > .herotext { grid-area: text; min-width: 0; align-self: center; }
/* A service's name is one word — tur_admin_public — and it set a phone's page width: 308 in 285 (2026-10-18). */
#servicePagehead > .herotext h2 { overflow-wrap: break-word; }
#servicePagehead > #itemActions {
  grid-area: actions; margin-left: var(--gap-5); max-width: 440px;
  display: flex; flex-wrap: wrap; justify-content: flex-end; align-content: flex-start; gap: var(--gap-2);
}
#itemActions > a.btn.primary { flex: 1 1 100%; text-align: center; }
#itemActions > button { padding: 6px 12px; }
#servicePagehead > #serviceTabs { grid-area: tabs; justify-self: start; align-self: end; }
#servicePagehead > #itemLayerField { grid-area: layer; align-self: end; margin: 0 0 8px var(--gap-4); }
#itemThumb .thumbcol { align-items: stretch; }
#itemThumb .thumb { width: 180px; height: 116px; border-radius: var(--round-lg); }

#serviceOverview.itemgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; }
#serviceOverview > .itemmain { display: contents; }
#serviceOverview > #serviceDetails { grid-column: 1; grid-row: 1 / span 2; order: 0; }
#serviceOverview #serviceHead { grid-column: 2; grid-row: 1 / span 2; }
#serviceOverview #serviceHead .itemhead { display: block; }
#serviceOverview #serviceLayersHead {
  grid-column: 3; grid-row: 1; margin: 0; padding: var(--gap-4) var(--gap-5) var(--gap-2);
  background: var(--surface); border: 1px solid var(--rule); border-bottom: 0;
  border-radius: var(--round-lg) var(--round-lg) 0 0;
}
#serviceOverview #serviceLayersPanel { grid-column: 3; grid-row: 2; margin: 0; border-top: 0; border-radius: 0 0 var(--round-lg) var(--round-lg); }
#serviceOverview h4 { margin-top: 0; }

/* An image service has no Layers card, so its two cards share the row rather than leaving a third of it empty
   (UX sweep 2026-10-19). */
@media (min-width: 1241px) {
  #serviceOverview.itemgrid:has(> .itemmain > #serviceLayersPanel[hidden]) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 1240px) {
  #serviceOverview.itemgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  #serviceOverview #serviceLayersHead { grid-column: 1 / -1; grid-row: 3; }
  #serviceOverview #serviceLayersPanel { grid-column: 1 / -1; grid-row: 4; }
}
@media (max-width: 900px) {
  #view-service #servicePagehead {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas: "thumb text" "actions actions" "layer layer" "tabs tabs";
  }
  #servicePagehead > #itemActions { margin-left: 0; max-width: none; justify-content: flex-start; }
  #itemActions > a.btn.primary { flex: 0 1 auto; }
  #servicePagehead > #itemLayerField { margin: 0; }
  #itemThumb .thumb { width: 120px; height: 78px; }
  #serviceOverview.itemgrid { grid-template-columns: minmax(0, 1fr); }
  #serviceOverview > #serviceDetails, #serviceOverview #serviceHead,
  #serviceOverview #serviceLayersHead, #serviceOverview #serviceLayersPanel { grid-column: 1; grid-row: auto; }
}
@media (max-width: 480px) {
  #itemThumb .thumbcol .tiny { font-size: 12px; padding: 3px 6px; }
  #itemThumb .thumb { width: 88px; height: 58px; }
}

/* ═══════════════════════════════════════════ Dense lists (owner's reference, 2026-10-16) */

/*
  <b>A row is a line, not a card.</b> The reference lists ten services in the height ours gave three: a small
  picture, the name, then short columns. The picture stays — it is how a reader finds a layer at a glance — at a
  size that sets the row's height to one line of text and its caption.
*/

#services td, #contentRows td { padding-top: 8px; padding-bottom: 8px; vertical-align: middle; }
#services td:first-child { padding-right: 4px; }
#services .name { font-size: var(--t-body); font-weight: 600; }
#services td.type, #contentRows td.type { color: var(--muted); }
#view-services .toolbar select { flex: none; }

/* Server's page head sits over the list, beside the column, as Server Manager's does. */
#serverMain > .pagehead { margin-top: 0; }


/* ═══════════════════════════════════════════ Server's service page: head card and tabs (owner's reference, 2026-10-16) */

/*
  <b>Server Manager's service page: a head card, then the pages as tabs.</b> The four pages — General ·
  Parameters · Capabilities · Layers — were a left list inside the panel; the reference runs them along the top,
  under the service's picture, name, state and address, with Start or Stop on the right.
*/
#serverHero {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start;
  column-gap: var(--gap-5); row-gap: var(--gap-3); margin: 0 0 var(--gap-4);
}
#serverHero .thumb { width: 180px; height: 116px; border-radius: var(--round-lg); }
#serverHero .herotext h2 { margin: 0 0 4px; }
#serverHero .herotext p { margin: 0 0 var(--gap-3); color: var(--muted); display: flex; align-items: center; gap: var(--gap-2); flex-wrap: wrap; }
#serverHero .urlrow { display: flex; align-items: center; gap: var(--gap-2); max-width: 760px; }
#serverHero .urlrow .key { font-size: var(--t-small); color: var(--muted); white-space: nowrap; }
#serverHero .urlrow input { flex: 1; min-width: 0; font-family: var(--mono); font-size: 12.5px; background: var(--surface-2); }
#serverHero .heroactions { display: flex; gap: var(--gap-2); flex-wrap: wrap; justify-content: flex-end; }
/* UX sweep 2026-10-19, findings 7, 8 and 11: the name and the address wrap rather than widen the page, a failing
   service says so under its name, and a stopped one gets a neutral line instead of a red toast. */
#serverHero .herotext { min-width: 0; }
#serverHero .herotext h2 { overflow-wrap: anywhere; }
#serverHero .urlrow { flex-wrap: wrap; }
#serverHero .urlrow code {
  flex: 1 1 260px; min-width: 0; padding: 5px 8px; border: 1px solid var(--rule); border-radius: var(--round);
  background: var(--surface-2); font-family: var(--mono); font-size: 12.5px; overflow-wrap: anywhere; color: var(--ink);
}
#serverHero:not(:has(.herothumb)) { grid-template-columns: minmax(0, 1fr) auto; }
@media (max-width: 760px) {
  #serverHero:not(:has(.herothumb)) { grid-template-columns: minmax(0, 1fr); }
  #serverHero:not(:has(.herothumb)) .heroactions { grid-column: 1; }
}
#serverHero .herofail { color: var(--ink); }
#serverHero .herofail a { text-decoration: underline; }
#serverHero .thumb.empty { display: grid; place-items: center; color: var(--muted); font-size: var(--t-small); }
#serverHero .heroactions .btn svg { width: 13px; height: 13px; margin-left: 6px; vertical-align: -1px; }
.stoppednote {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2) var(--gap-4); margin: 0 0 var(--gap-4);
  padding: var(--gap-3) var(--gap-4); border: 1px solid var(--rule-strong); border-radius: var(--round-lg);
  background: var(--surface-2); color: var(--ink);
}
.stoppednote[hidden] { display: none; }
.stoppednote p { flex: 1 1 260px; margin: 0; }
#serviceLimits .row { margin-top: var(--gap-4); }

:root[data-surface="server"] #serviceEdit { display: block; }
:root[data-surface="server"] #serviceNav {
  display: flex; flex-direction: row; gap: 0; width: auto; padding: 0 var(--gap-4);
  border-right: 0; border-bottom: 1px solid var(--rule); background: none; overflow-x: auto;
}
:root[data-surface="server"] #serviceNav a {
  padding: 12px 16px; margin-bottom: -1px; border: 0; border-bottom: 2px solid transparent; border-radius: 0;
  background: none; color: var(--muted); white-space: nowrap; font-weight: 500;
}
:root[data-surface="server"] #serviceNav a:hover { color: var(--ink); background: none; }
:root[data-surface="server"] #serviceNav a[aria-current] {
  color: var(--accent-ink); border-bottom-color: var(--accent); background: none; font-weight: 600;
}

@media (max-width: 900px) {
  #serverHero { grid-template-columns: auto minmax(0, 1fr); }
  #serverHero .heroactions { grid-column: 1 / -1; justify-content: flex-start; }
  #serverHero .thumb { width: 104px; height: 70px; }
}
@media (max-width: 520px) {
  #serverHero .urlrow .key { display: none; }
}

/* The list's short columns read in the body face, as the reference sets them; mono stays for addresses and code. */
#contentRows td.val, #services td.val, #contentCount, #serviceCount { font-family: var(--sans); }
/* The Layers card's head and rows are two grid items; the spare height of the tall cards goes under them. */
#serviceOverview.itemgrid { grid-template-rows: auto 1fr; }
@media (max-width: 1240px) { #serviceOverview.itemgrid { grid-template-rows: auto 1fr auto auto; } }
@media (max-width: 900px) { #serviceOverview.itemgrid { grid-template-rows: none; } }

/* The surface's name beside the product's, in plain grey, as the reference sets it — not a second badge. */
#brand .badge, :root[data-theme="dark"] #brand .badge {
  background: none; border: 0; padding: 0; color: var(--muted); font-size: 14px; font-weight: 500;
}
#serviceOverview #serviceLayersPanel { margin-top: calc(-1 * var(--gap-5)); }
@media (max-width: 900px) { #serviceOverview #serviceLayersPanel { margin-top: calc(-1 * var(--gap-5)); } }

/* ── The design review of the restructured screens (2026-10-16) ── */

/* Dark: the current place in the accent blue. Plain links take --link in both themes, so a component that sets its own
   colour (a tab strip, a side list) keeps it; a blanket a:not(.btn) here painted every inactive tab blue (sweep 2026-10-19). */
:root[data-theme="dark"] #tabs a[data-tab][aria-current="page"],
:root[data-theme="dark"] #serviceTabs.segmented a[aria-current],
:root[data-theme="dark"] #mapTabs.segmented a[aria-current],
:root[data-theme="dark"] .sidelist a[aria-current="page"] { color: var(--accent-ink); }

/* The heads are cards, as the reference draws them. */
#view-service #servicePagehead, #serverHero {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--round-lg);
  padding: var(--gap-4) var(--gap-5) 0; box-shadow: var(--lift);
}
#serverHero { padding-bottom: var(--gap-4); }
#view-service #servicePagehead { margin-bottom: var(--gap-5); }
/* The main action is as wide as its words, beside the others. */
#itemActions > a.btn.primary { flex: 0 1 auto; }
/* Card headings in sentence case, as the reference's. */
#serviceOverview h4 {
  text-transform: none; letter-spacing: 0; font-size: 15px; font-weight: 600; color: var(--ink);
}
#newMap .plus { margin-right: 4px; }
input[type="checkbox"] { accent-color: var(--accent); }
.facets input[type="checkbox"] { width: 16px; height: 16px; }

/* Narrow: the list comes before the health and resource cards, which stack after it. */
@media (max-width: 1080px) {
  #serverGrid > div:first-child { display: contents; }
  #serverGrid > div:first-child > #healthWidget, #serverGrid > div:first-child > #resourceWidget { order: 3; }
  #serverMain { order: 2; }
}
@media (max-width: 480px) {
  :root[data-surface="server"] #serviceNav { padding: 0 4px; }
  :root[data-surface="server"] #serviceNav a { padding: 12px 10px; }
}

/* ═══════════════════════════════════════════ The header: a utility line over the bar (owner, 2026-10-16) */
header:has(#utilbar) {
  display: grid; height: auto; min-height: 0; padding: 0 clamp(var(--gap-3), 3vw, 44px);
  grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "util util" "brand tabs";
  column-gap: var(--gap-5); row-gap: 0; align-items: stretch;
}
header:has(#utilbar) > #brand { grid-area: brand; align-self: center; padding: 6px 0 8px; }
header #tabs { grid-area: tabs; margin: 0; align-self: stretch; min-height: 48px; border-top: 0;
               overflow-x: auto; scrollbar-width: none; }
header #tabs a[data-tab], header #tabs a[data-link] { padding-top: 0; padding-bottom: 0; }
#utilbar {
  grid-area: util; display: flex; align-items: center; justify-content: flex-end; gap: var(--gap-3);
  min-height: 30px; font-size: 12.5px; color: var(--muted); min-width: 0;
  border-bottom: 1px solid var(--rule); margin: 0 calc(-1 * clamp(var(--gap-3), 3vw, 44px));
  padding: 0 clamp(var(--gap-3), 3vw, 44px); background: var(--surface-2);
}
#utilbar > #healthLine, :root[data-surface] #utilbar > #healthLine {
  display: block; flex: 0 1 auto; min-width: 0; max-width: 46ch; margin-right: auto;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12.5px; order: 0;
}
#utilbar > #healthDot { order: -1; }
#utilbar #surfaces { background: none; border: 0; padding: 0; gap: 0; }
#utilbar #surfaces a {
  padding: 2px 8px; font-size: 12.5px; color: var(--muted); background: none; border-radius: 0; box-shadow: none;
}
#utilbar #surfaces a + a { border-left: 1px solid var(--rule-strong); }
#utilbar #surfaces a[aria-current], :root[data-theme="dark"] #utilbar #surfaces a[aria-current] {
  background: none; color: var(--accent-ink); font-weight: 600;
}
#utilbar .right { margin-left: 0; gap: var(--gap-3); }
#utilbar #who, #utilbar #who b { display: inline; font-size: 12.5px; text-align: left; max-width: none; }
#utilbar #who b { margin-right: 4px; }
#utilbar #themeToggle, #utilbar #signout {
  min-height: 0; padding: 0; border: 0; background: none; box-shadow: none;
  font-size: 12.5px; font-weight: 500; color: var(--accent-ink); cursor: pointer;
}
#utilbar #themeToggle:hover, #utilbar #signout:hover { text-decoration: underline; }
#utilbar .right > * + * { padding-left: var(--gap-3); border-left: 1px solid var(--rule-strong); }
@media (max-width: 760px) {
  #utilbar { gap: var(--gap-2); }
  #utilbar > #healthLine, :root[data-surface] #utilbar > #healthLine { display: none; }
  #utilbar #who { max-width: 34vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  #utilbar #who span, #utilbar #who small { display: none; }
  header:has(#utilbar) { grid-template-areas: "util util" "brand brand" "tabs tabs"; }
  header #tabs { min-height: 44px; border-top: 1px solid var(--rule);
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); }
}
/* The health sentence is the dot's tooltip and label, not a line of its own (owner, 2026-10-16: *"platform store
   reachable bla bla'sı ne?"*). The dot turns red when something is wrong; hovering it says what. */
#utilbar > #healthLine, :root[data-surface] #utilbar > #healthLine { display: none; }
#utilbar > #healthDot { margin-right: auto; }
/* <b>The health in a word, linking to Site › Status</b> (UX sweep 2026-10-19, findings 1 and 14). The dot alone was
   colour and hover only, and stayed green while a service failed. The word carries the state; the dot or the
   triangle repeats it for the eye. */
.healthpill {
  display: inline-flex; align-items: center; min-height: 24px; padding: 1px 9px; border: 1px solid var(--rule);
  border-radius: 999px; background: var(--surface); color: var(--muted); font-size: 12px; font-weight: 600;
  text-decoration: none; white-space: nowrap;
}
.healthpill #healthWord { display: inline-flex; align-items: center; gap: 6px; }
.healthpill svg { width: 13px; height: 13px; flex: none; }
.healthpill:hover { text-decoration: underline; }
.healthpill.ok { color: var(--ok); border-color: var(--ok-line); background: var(--ok-soft); }
.healthpill.failing, .healthpill.unusable { color: var(--alert); border-color: var(--alert-line); background: var(--alert-soft); }
.healthpill[hidden] { display: none; }

/* ═══════════════════════════════════════════ The account menu (owner, 2026-10-16) */
#utilbar .usermenu > summary {
  display: flex; align-items: center; gap: 6px; padding: 2px 6px; font-size: 12.5px; color: var(--ink);
}
#utilbar .usermenu .initial {
  width: 20px; height: 20px; border-radius: 50%; background: var(--accent); color: #fff;
  display: grid; place-items: center; font-size: 11px; font-weight: 700;
}
#utilbar .usermenu .caret { color: var(--muted); font-size: 10px; }
#utilbar .usermenu .sheet { min-width: 240px; font-size: var(--t-small); }
#utilbar .usermenu #who {
  display: block; padding: 8px 10px 10px; margin-bottom: 4px; border-bottom: 1px solid var(--rule);
  color: var(--muted); font-size: var(--t-small); white-space: normal; max-width: none; overflow: visible;
}
#utilbar .usermenu #who b { display: block; color: var(--ink); font-size: var(--t-body); margin: 0 0 2px; }
#utilbar .usermenu #themeToggle, #utilbar .usermenu #signout {
  display: block; width: 100%; padding: 8px 10px; border-radius: 3px; text-align: left;
  font-size: var(--t-small); color: var(--ink); text-decoration: none; border-left: 0;
}
#utilbar .usermenu #themeToggle:hover, #utilbar .usermenu #signout:hover { background: var(--surface-2); text-decoration: none; }
#utilbar .usermenu #themeToggle .ico { display: inline-block; width: 1.2em; }
#utilbar .right > .usermenu { padding-left: 0; border-left: 0; }
@media (max-width: 760px) { #utilbar .usermenu #userName { display: none; } }

/* <b>The header's height is a token</b>, because the symbology editor and the map composer size themselves to the
   window under it — they assumed 58 pixels, and the utility line made it 80 (three Console tests found it). */
:root { --header-h: 80px; }
header:has(#utilbar) { box-sizing: border-box; height: var(--header-h); grid-template-rows: 30px minmax(0, 1fr); }
@media (max-width: 760px) {
  :root { --header-h: 124px; }
  header:has(#utilbar) { grid-template-rows: 30px 44px 50px; }
}
#utilbar .usermenu #themeToggle { display: flex; align-items: center; gap: 8px; }
#utilbar .usermenu #themeToggle .ico { display: inline-flex; width: auto; color: var(--muted); }


#contentRows .thumb { width: 56px; height: 38px; border-radius: var(--round); }

/* ═══════════════════════════════════════════ Services as media rows (owner, 2026-10-17: "estetik bir düzen") */

/*
  <b>Server Manager's list, in this console's colours.</b> A picture, the name and its kind, two lines of what the
  service says about itself, one quiet line of facts, and the verbs as icons at the right. No column headings:
  every fact is labelled by what it looks like or by its tooltip, so nothing pushes the row wider than the panel.
*/
#view-services .widetable thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
#services tr.svcrow td { padding: 14px 16px; vertical-align: top; border-bottom: 1px solid var(--rule); }
#services tr.svcrow:last-child td { border-bottom: 0; }
#services tr.svcrow td.thumbcell { width: 1%; padding-right: 0; }
#services tr.svcrow .thumb { width: 104px; height: 70px; border-radius: var(--round-lg); }
#services td.svcbody { width: 100%; max-width: 0; }
#services .svctitle { display: flex; align-items: baseline; gap: var(--gap-2); min-width: 0; }
#services .svctitle .name {
  font-size: 16px; font-weight: 600; color: var(--accent-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
#services tr.svcrow:hover .svctitle .name { text-decoration: underline; }
#services .svctype { color: var(--muted); font-size: var(--t-small); white-space: nowrap; flex: none; }
#services .svcdesc {
  margin: 4px 0 0; color: var(--ink); font-size: var(--t-small); line-height: 1.45;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
}
#services .svcmeta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 0; margin-top: 8px;
  color: var(--muted); font-size: 12.5px;
}
#services .svcmeta .sep { margin: 0 8px; color: var(--faint); }
#services .svcmeta > span, #services .svcmeta > a { display: inline-flex; align-items: center; gap: 5px; }
#services .svcmeta svg { width: 13px; height: 13px; }
/* The facts are words, not badges: the state keeps its dot, the scope its globe or padlock. */
#services .svcmeta .pill {
  background: none; border: 0; padding: 0; min-height: 0; font-size: 12.5px; color: var(--muted); font-weight: 500;
}
#services .svcmeta .pill.p-started { color: var(--ok); }
#services .svcmeta .pill.p-stopped { color: var(--warn); }
#services .svcmeta a { color: inherit; }
#services .svcmeta a:hover .pill { color: var(--accent-ink); text-decoration: underline; }
#services .svcmeta select { font-size: 12.5px; padding: 1px 6px; }
#services td.acts { width: 1%; white-space: nowrap; vertical-align: top; text-align: right; }
#services td.acts > * { vertical-align: middle; }

.iconbtn {
  display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0;
  border: 1px solid transparent; border-radius: var(--round); background: none; color: var(--muted);
  cursor: pointer;
}
.iconbtn:hover:not([disabled]) { background: var(--surface-2); color: var(--ink); border-color: var(--rule); text-decoration: none; }
.iconbtn[disabled] { opacity: .4; cursor: not-allowed; }
.iconbtn.placeholder { cursor: default; }
.iconbtn[data-to="stop"] { color: var(--muted); }
.iconbtn[data-to="start"] { color: var(--ok); }
#services td.acts .menu > summary { padding: 7px 9px; }

/* The summary over the list, where the donut was. */
.svcsummary {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 0; margin: 0 0 var(--gap-3);
  color: var(--muted); font-size: var(--t-small);
}
.svcsummary .sep { margin: 0 10px; color: var(--faint); }
.svcsummary .dot { margin-right: 6px; }
.svcsummary b { color: var(--ink); margin-right: 4px; }
.svcsummary a { margin-left: auto; }

/* Site › Status holds the health and the resources, side by side. */
.widgetrow { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--gap-4); margin: 0 0 var(--gap-5); }
.widgetrow > .widget { margin: 0; }

@media (max-width: 600px) {
  #services tr.svcrow td { padding: 12px 10px; }
  #services tr.svcrow .thumb { width: 64px; height: 44px; }
  #services .svcdesc { -webkit-line-clamp: 3; line-clamp: 3; }
  #services td.acts .iconbtn.placeholder { display: none; }
}
/* A media row needs no minimum and no pinned actions column: nothing in it can push the row wider. */
#view-services .widetable table { min-width: 0; }
#view-services .widetable table td:last-child, #view-services .widetable table th:last-child {
  position: static; box-shadow: none; background: transparent;
}
:root[data-theme="dark"] #view-services .widetable table td:last-child { background: transparent; }
@media (max-width: 600px) {
  #services tr.svcrow td.acts { padding-left: 0; }
  #services td.acts .iconbtn { width: 28px; height: 28px; }
  #services .svctitle { flex-wrap: wrap; row-gap: 0; }
}
@media (max-width: 600px) {
  #services .svcmeta { gap: 4px 12px; }
  #services .svcmeta .sep { display: none; }
}
/* <b>A click on an icon is a click on its button.</b> The row handlers read the target's own data attributes, and an
   SVG inside a button was the target — so the icon Stop and Map did nothing under a mouse while Enter worked. */
button svg, .iconbtn svg, a.iconbtn svg { pointer-events: none; }
/* The selected row's tint runs to the right edge (design review: the actions cell cut it short). */
#view-services .widetable table tr.pick.on td:last-child { background: var(--accent-soft); }
#services .svctitle a.name:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
@media (max-width: 600px) { #services .svcmeta { gap: 2px 10px; } }

/*
  <b>One type voice, 2026-10-17</b> (owner: *"estetik bir düzen istiyorum"*). Section labels and column headings
  were spaced capitals in the faintest grey, and every count, date and size was set in monospace — three
  registers on one card, which is what made the screens read as a debugging tool beside Server Manager's.
  Labels are sentence case at 14px in ink, column headings 12.5px in the muted grey, and numbers are the body
  face with tabular figures, so they still line up. Monospace is kept for what is typed or copied: code,
  addresses, identifiers, SQL.
*/

/* ═══════════════════════════════════════════ My content as media rows (owner, 2026-10-17) */
#contentRows tr.mediarow td { padding: 14px 16px; vertical-align: top; border-bottom: 1px solid var(--rule); }
#contentRows tr.mediarow:last-child td { border-bottom: 0; }
#contentRows tr.mediarow td.tick { padding-right: 0; padding-top: 18px; }
#contentRows tr.mediarow td.thumbcell { width: 1%; padding-right: 0; }
#contentRows tr.mediarow .thumb { width: 104px; height: 70px; border-radius: var(--round-lg); }
#contentRows tr.mediarow td.name { width: 100%; max-width: 0; }
#contentRows .svctitle { display: flex; align-items: baseline; gap: var(--gap-2); min-width: 0; }
#contentRows .svctitle > a {
  font-size: 16px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
#contentRows .svctype { color: var(--muted); font-size: var(--t-small); white-space: nowrap; flex: none; }
#contentRows .svcdesc {
  margin: 4px 0 0; color: var(--ink); font-size: var(--t-small); line-height: 1.45;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden;
}
#contentRows .svcmeta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 0; margin-top: 8px;
  color: var(--muted); font-size: 12.5px;
}
#contentRows .svcmeta .sep { margin: 0 8px; color: var(--faint); }
#contentRows .svcmeta .pill { background: none; border: 0; padding: 0; min-height: 0; font-size: 12.5px; color: var(--muted); font-weight: 500; }
#contentRows .svcmeta .pill.p-stopped { color: var(--warn); }
#contentRows .svcmeta .pillbtn { padding: 0; border: 0; background: none; min-height: 0; }
#contentRows .svcmeta .pillbtn:hover .pill { color: var(--accent-ink); text-decoration: underline; }
#contentRows td.acts { width: 1%; white-space: nowrap; text-align: right; }
#contentRows td.acts > * { vertical-align: middle; }
#contentRows .iconbtn.on { color: var(--accent-ink); background: var(--accent-soft); border-color: var(--accent-line); }
#view-content .widetable table { min-width: 0; }
#view-content .widetable table td:last-child, #view-content .widetable table th:last-child {
  position: static; box-shadow: none; background: transparent;
}
@media (max-width: 600px) {
  #contentRows tr.mediarow td { padding: 12px 8px; }
  #contentRows tr.mediarow .thumb { width: 64px; height: 44px; }
  #contentRows .svcmeta { gap: 2px 10px; }
  #contentRows .svcmeta .sep { display: none; }
  #contentRows .svctitle { flex-wrap: wrap; row-gap: 0; }
}
#view-content .widetable table th:last-child { background: var(--surface-2); }
/* An empty folder list is one line: the label, "none yet", and New folder at its end. */
#contentSide > #contentFolders:not(:has(.chip)) { flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 6px; }
#contentSide > #contentFolders:not(:has(.chip)) .folderlabel { margin-bottom: 0; }
#contentSide > #contentFolders:not(:has(.chip)) #folderNew { margin: 0 0 0 auto; padding: 2px 8px; }

/* The item's secondary actions, one menu (owner, 2026-10-17). It looks like the buttons beside it. */
#itemActions .itemmore > summary, #mapActions .itemmore > summary {
  display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; font-size: var(--t-small);
  color: var(--ink); border: 1px solid var(--control-edge); border-radius: var(--round); background: var(--surface);
  font-weight: 500; line-height: 1.4;
}
#itemActions .itemmore[open] > summary, #itemActions .itemmore > summary:hover,
#mapActions .itemmore[open] > summary, #mapActions .itemmore > summary:hover { background: var(--surface-2); }
#itemActions .itemmore .sheet, #mapActions .itemmore .sheet { min-width: 200px; }
#itemActions .itemmore .sheet button { display: block; width: 100%; text-align: left; }

/* Logs: the head sits over the log, beside its sources, as on every other page with a left column (2026-10-17);
   the count and refresh are a plain line under the explanation, not a card of their own. */
#view-logs.on > .lede + .toolbar, #view-logs.on > #logSources + .toolbar {
  background: none; border: 0; box-shadow: none; padding: 0; margin: 0 0 var(--gap-2); min-height: 0;
}

/* Services' ticks (owner, 2026-10-17): the heading row comes back, slim, to carry Select all. */
#view-services .widetable thead { position: static; width: auto; height: auto; overflow: visible; clip: auto; }
#view-services .widetable thead th { padding-top: 10px; padding-bottom: 10px; }
#services tr.svcrow td.tick, #view-services th.tick { width: 1%; padding-right: 0; text-align: center; }
#services tr.svcrow td.tick { padding-top: 18px; }
#view-services th.tick + th + th { padding-left: 0; }
#serviceBulk button.tiny svg { width: 12px; height: 12px; vertical-align: -1px; margin-right: 2px; }
#view-services .widetable table th:last-child { background: var(--surface-2); }

/* A service row's verbs, each an icon and its word (owner, 2026-10-17: the bare icons were not found). */
.rowbtn {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; min-height: 30px;
  border: 1px solid var(--rule-strong); border-radius: var(--round); background: var(--surface);
  color: var(--ink); font-size: 13px; font-weight: 500; line-height: 1; cursor: pointer; text-decoration: none;
}
.rowbtn svg { width: 13px; height: 13px; pointer-events: none; flex: none; }
.rowbtn:hover:not([disabled]) { background: var(--surface-2); border-color: var(--control-edge); text-decoration: none; }
.rowbtn[disabled] { opacity: .45; cursor: not-allowed; }
.rowbtn[data-to="start"] { color: var(--ok); }
.rowbtn.danger { color: var(--alert); }
#services td.acts { display: table-cell; }
#services td.acts > .rowbtn + .rowbtn { margin-left: 6px; }
@media (max-width: 760px) {
  #services td.acts { white-space: normal; }
  #services td.acts > .rowbtn { margin: 0 0 6px 0 !important; width: 100%; justify-content: flex-start; }
}

/* UX sweep 2026-10-19, findings 1-4 and 18: a verb only when it can act, the reason in words when it cannot, a link
   that says it leaves for Studio, names that wrap rather than clip, and a failing service that reads as one. */
#services .svctitle .name { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
#services .svcdesc { text-overflow: ellipsis; overflow-wrap: anywhere; }
#services td.acts > * + * { margin-left: 6px; }
.rowlink {
  display: inline-flex; align-items: center; gap: 5px; min-height: 30px; padding: 0 2px;
  color: var(--link); font-size: 13px; font-weight: 500; text-decoration: underline; text-underline-offset: 2px;
}
.rowlink svg { width: 13px; height: 13px; flex: none; }
.rowwhy {
  display: block; margin: 6px 0 0; white-space: normal; text-align: left;
  color: var(--muted); font-size: 12.5px; line-height: 1.4;
}
.rowwhy a { text-decoration: underline; }
#services tr.svcrow.failing td,
:root[data-theme="dark"] #view-services .widetable #services tr.svcrow.failing td:last-child { background: var(--alert-soft); }
#services .svcmeta .pill.p-failing { color: var(--alert); font-weight: 600; }
#services .svcfail { margin: 8px 0 0; color: var(--ink); font-size: var(--t-small); line-height: 1.45; }
#services .svcfail a { text-decoration: underline; }
#services .thumb.empty { display: grid; place-items: center; color: var(--muted); font-size: 11.5px; }
.svcsummary .failword { display: inline-flex; align-items: center; gap: 5px; color: var(--alert); font-weight: 600; }
.svcsummary .failword svg { width: 13px; height: 13px; }
.bulkbar .bulkwhy { color: var(--muted); font-size: 12.5px; }
.systag {
  display: inline-block; margin-left: 4px; padding: 0 5px; border: 1px solid var(--rule-strong); border-radius: 4px;
  color: var(--muted); font-size: 11px; font-weight: 600; line-height: 16px; vertical-align: 1px;
}
/* A phone: the row is a card — tick, picture and name on top, the verbs on their own line under them. */
@media (max-width: 600px) {
  /* The table and its body flow as blocks: a grid row inside a table sized to its content gave the name 0 px. */
  #view-services .widetable table, #services { display: block; width: 100%; }
  #services tr.svcrow { display: grid; grid-template-columns: auto auto minmax(0, 1fr); }
  #services tr.svcrow td { display: block; border-bottom: 0; }
  #services tr.svcrow { border-bottom: 1px solid var(--rule); }
  #services td.svcbody { max-width: none; width: auto; }
  #services tr.svcrow td.acts { grid-column: 1 / -1; width: auto; padding-top: 0; text-align: left;
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
  #services td.acts > * + * { margin-left: 0; }
  #services td.acts > .rowbtn { width: auto; margin: 0 !important; }
}
/* Folders as a row of chips under 1080px, so the first service is on the first screen of a phone. */
@media (max-width: 1080px) {
  #folders { display: flex; gap: 6px; overflow-x: auto; padding: var(--gap-3); scrollbar-width: thin; }
  #folders .rail-item { flex: none; padding: 8px 12px; min-height: 40px; border: 1px solid var(--rule);
    border-radius: var(--round); white-space: nowrap; }
  #folders .rail-item.on { border-color: var(--accent-line); }
  #folders .rail-name { overflow: visible; }
  #folders .rail-count { margin-left: 6px; }
}
#view-services .widetable table tr.pick:hover td:last-child { background: var(--surface-2); }
#view-services .widetable table tr.pick.on:hover td:last-child { background: var(--accent-soft); }
/* The list of domains (ADR-087 §6, 2026-10-17): under the designed fields, and in the Fields page's dialog. */
.domainspanel { margin-top: var(--gap-4); padding-top: var(--gap-3); border-top: 1px solid var(--rule); }
.domainshead { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.domainshead h4 { margin: 0; }
.domainstable { overflow-x: auto; }
#domainsDialog { width: min(820px, calc(100vw - 32px)); }
#domainsDialog .domainspanel { margin-top: 0; padding-top: 0; border-top: 0; }
#dFields .fDomain { min-width: 9em; }

/* Organization (2026-10-17): Members and Settings across the top, over both columns of a settings page. */
.view > nav.orgstrip { margin-bottom: var(--gap-5); }
.view.withside > nav.orgstrip { grid-column: 1 / -1; grid-row: 1; }
.view.withside > nav.orgstrip ~ nav.subpages { grid-row: 2 / span 60; }
/* A section inside a page — Security's Sign-in and Apps — under the page's own head. */
.pagehead.sub { margin: var(--gap-6) 0 var(--gap-4); }
.pagehead + .pagehead.sub { margin-top: 0; }
.pagehead.sub h3 { margin: 0; font-size: 17px; }

/* Members, as Portal's Members page (2026-10-17): filters down the left, the list beside them. */
.memberslayout { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: var(--gap-5); align-items: start; }
.memberfilters { position: sticky; top: 96px; }
.memberfilters h4 { margin: 0 0 var(--gap-2); }
/* The wrapper reads as the section's heading on a wide screen and as a button on a narrow one. */
.memberfilters details.mfwrap { border-top: 0; padding: 0; }
.memberfilters .mfwrap > summary { list-style: none; font-size: var(--t-body); padding: 0 0 var(--gap-2); }
.memberfilters .mfwrap > summary::-webkit-details-marker { display: none; }
.mfon { color: var(--muted); font-weight: 400; margin-left: 4px; }
.mfon:empty { display: none; }
@media (min-width: 1024px) { .memberfilters .mfwrap > summary { cursor: default; } }
@media (max-width: 1023px) {
  .memberfilters .mfwrap > summary {
    display: inline-flex; align-items: center; min-height: 40px; padding: 0 var(--gap-4); margin-bottom: var(--gap-2);
    border: 1px solid var(--control-edge); border-radius: var(--round); background: var(--surface); cursor: pointer;
  }
  .memberfilters .mfwrap > summary::after { content: "\25BE"; margin-left: var(--gap-2); color: var(--muted); }
  .memberfilters .mfwrap[open] > summary::after { content: "\25B4"; }
}
.memberfilters details { border-top: 1px solid var(--rule); padding: 8px 0; }
.memberfilters summary { cursor: pointer; font-weight: 600; font-size: 14px; padding: 2px 0; }
.memberfilters select, .memberfilters input[type=search] { width: 100%; margin-top: 6px; }
.mflist { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; max-height: 220px; overflow-y: auto; }
.mflist label { display: flex; gap: 8px; align-items: center; font-size: 14px; cursor: pointer; }
#view-members .toolbar input[type=search] { flex: 0 1 360px; }
/* Below 720 pixels the headings are not drawn and not in the Tab order; their two jobs are this bar. */
.narrowbar { display: none; }
@media (max-width: 719px) {
  #view-members table.rowblocks thead { display: none; }
  .narrowbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2) var(--gap-3); margin: 0 0 var(--gap-3); }
  .narrowbar label.check { display: inline-flex; align-items: center; gap: var(--gap-2); min-height: 44px; margin-right: auto; }
  .narrowbar input[type=checkbox] { width: 24px; height: 24px; margin: 0; }
  .narrowbar select { min-height: 40px; }
}
/* A label and the control it names wrap as one, so "Order" never sits apart from its list. */
.sortpair { display: inline-flex; align-items: center; gap: var(--gap-2); flex: none; }
#view-members .sortbtn { all: unset; cursor: pointer; font: inherit; color: inherit; }
#view-members .sortbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
#view-members th[aria-sort] .sortbtn { color: var(--accent-ink); }
#view-members th[aria-sort="ascending"] .sortbtn::after { content: " \25B2"; font-size: 10px; }
#view-members th[aria-sort="descending"] .sortbtn::after { content: " \25BC"; font-size: 10px; }
#members tr.isoff td.name { opacity: .65; }
/* Fixed columns, so Last login does not move 40 pixels between one filter and the next (sweep 2026-10-19). */
@media (min-width: 720px) {
  #view-members table.rowblocks { table-layout: fixed; }
  #view-members table.rowblocks th:nth-child(1) { width: 44px; }
  #view-members table.rowblocks th:nth-child(3) { width: 132px; }
  #view-members table.rowblocks th:nth-child(4) { width: 120px; }
  #view-members table.rowblocks th:nth-child(5) { width: 170px; }
  #view-members table.rowblocks th:nth-child(6) { width: 60px; }
  #members td.name { overflow-wrap: anywhere; }
}
details.rowmenu { display: inline-block; }
details.rowmenu > summary {
  list-style: none; cursor: pointer; padding: 4px 10px; border-radius: var(--round); font-size: 18px; line-height: 1;
}
details.rowmenu > summary::-webkit-details-marker { display: none; }
details.rowmenu > summary:hover, details.rowmenu[open] > summary { background: var(--surface-2); }
details.rowmenu > .rowmenubody {
  position: fixed; z-index: 50; min-width: 200px; display: flex; flex-direction: column; padding: 6px; text-align: left;
  background: var(--surface); border: 1px solid var(--rule-strong); border-radius: var(--round); box-shadow: var(--lift);
}
details.rowmenu > .rowmenubody button {
  all: unset; cursor: pointer; padding: 8px 10px; border-radius: var(--round); font-size: 14px; color: var(--ink); text-align: left;
}
details.rowmenu > .rowmenubody button:hover, details.rowmenu > .rowmenubody button:focus-visible { background: var(--surface-2); }
details.rowmenu > .rowmenubody button.danger { color: var(--alert); }
details.rowmenu > .rowmenubody .hint { padding: 8px 10px; }
@media (max-width: 1023px) {
  .memberslayout { grid-template-columns: minmax(0, 1fr); gap: var(--gap-3); }
  .memberfilters { position: static; }
}

/* Member categories and profiles (2026-10-17). */
.memberfilters .mfedit { margin: 4px 0 2px; }
ul.cattree { list-style: none; margin: 0; padding: 0; }
ul.cattree ul.cattree { padding-left: 18px; }
ul.cattree label { display: flex; gap: 8px; align-items: center; padding: 2px 0; font-size: 14px; cursor: pointer; }
#catDialog { width: min(560px, calc(100vw - 32px)); }
#catDialogBody .catedit { display: flex; gap: 8px; align-items: center; padding: 3px 0; }
#catDialogBody .catedit input { flex: 1; min-width: 0; }
#catDialogBody .catnew { display: flex; gap: 8px; align-items: center; margin-top: var(--gap-3); flex-wrap: wrap; }
#catDialogBody .catnew input[type=text] { flex: 1; min-width: 160px; }
#members td.tick, #view-members th.tick { width: 1%; padding-right: 0; }
img.avatar.photo { object-fit: cover; padding: 0; }
.avatar.big, img.avatar.big { width: 96px; height: 96px; font-size: 32px; border-radius: 50%; }
#view-user .profilecard {
  max-width: 560px; margin: 0 auto; background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--round-lg, 10px); padding: var(--gap-5);
}
#view-user .profilehead { text-align: center; border-bottom: 1px solid var(--rule); padding-bottom: var(--gap-4); margin-bottom: var(--gap-4); }
#view-user .profilephoto { display: flex; flex-direction: column; align-items: center; gap: 8px; margin-bottom: var(--gap-3); }
#view-user .profilehead h2 { margin: 0; }
#view-user .profileform { display: flex; flex-direction: column; gap: 6px; }
#view-user .profileform label { font-weight: 600; font-size: 14px; margin-top: 8px; }
#view-user .profilefacts { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: var(--gap-4) 0 0;
  padding-top: var(--gap-4); border-top: 1px solid var(--rule); }
#view-user .profilefacts dt { color: var(--muted); }
#view-user .profilefacts dd { margin: 0; }
#view-user .profilebio { white-space: pre-wrap; }
/* The name takes focus on arrival so a screen reader starts there; a ring round it read as an editable field
   right above the real one (sweep 2026-10-19). */
#profileName:focus { outline: none; }
#photoDialog { width: min(560px, calc(100vw - 32px)); }
#photoDialog #photoPreview { width: 120px; height: 120px; object-fit: cover; border-radius: 50%; }
.usermenu .sheet a#myProfile { display: block; padding: 8px 12px; color: var(--ink); text-decoration: none; }
.usermenu .sheet a#myProfile:hover { background: var(--surface-2); }
details.rowmenu > .rowmenubody a { padding: 8px 10px; border-radius: var(--round); font-size: 14px; color: var(--ink); text-decoration: none; }
details.rowmenu > .rowmenubody a:hover { background: var(--surface-2); }

#view-user .profileform textarea { font: inherit; }

/* New group, as a dialog (2026-10-17). */
#groupNewDialog { width: min(560px, calc(100vw - 32px)); }
#groupNewDialog .picker { display: flex; flex-direction: column; gap: 6px; border: 0; padding: 0; margin: 0; background: none; }
#groupNewDialog .picker label { margin-top: 6px; }
#groupNewDialog select { font: inherit; }
#groupNewDialog .picker .hint { margin: 0; }
#groupNewDialog fieldset.pickset { margin-top: var(--gap-5); }
/* A group's name in the list is a link and looks like one, in both themes (sweep 2026-10-19). */
#groupRows td.name a { color: var(--link); }

/* Every "new" is a dialog (2026-10-17). */
dialog.formdialog { width: min(640px, calc(100vw - 32px)); }
#idpDialog { width: min(820px, calc(100vw - 32px)); }
dialog.formdialog .picker { display: flex; flex-direction: column; gap: 6px; border: 0; padding: 0; margin: 0; background: none; }
dialog.formdialog .picker label { margin-top: 6px; }
dialog.formdialog select, dialog.formdialog textarea { font: inherit; }
#memberDialog #memberPages { border: 0; padding: 0; }
#domainEditDialog .valuesedit { border: 0; padding: 0; margin: 0; background: none; }

/* Studio's search (ADR-196): a box at the end of the bar, and the results page. */
header:has(#utilbar) {
  grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "util util util" "brand tabs search";
}
header #globalSearch { grid-area: search; display: flex; align-items: center; margin: 0; }
header #globalSearch[hidden] { display: none; }
#globalSearch input { width: 280px; max-width: 28vw; }
@media (max-width: 760px) {
  header:has(#utilbar) { grid-template-areas: "util util util" "brand brand search" "tabs tabs tabs"; }
}
/* <b>Up to 900 the tabs take their own row when the search box is in the bar</b> (verification round, 2026-10-19):
   at 768 the box left the tabs about 360 pixels and cut Organization off. The header's height token follows, since
   the editors size themselves under it. */
@media (min-width: 761px) and (max-width: 900px) {
  :root:has(header #globalSearch:not([hidden])) { --header-h: 124px; }
  header:has(#globalSearch:not([hidden])) {
    grid-template-areas: "util util util" "brand brand search" "tabs tabs tabs"; grid-template-rows: 30px 44px 50px;
  }
  header:has(#globalSearch:not([hidden])) #tabs { border-top: 1px solid var(--rule); }
}
#view-search .toolbar input[type=search] { flex: 0 1 480px; }
#view-search code.itemid { font-size: 12px; user-select: all; }
/* A content hit is a Content row in small: the picture, the title and its type, one quiet line (UX sweep 2026-10-19). */
#searchRows tr.mediarow td { padding: 12px 16px; vertical-align: top; border-bottom: 1px solid var(--rule); }
#searchRows tr.mediarow:last-child td { border-bottom: 0; }
#searchRows tr.mediarow td.thumbcell { width: 1%; padding-right: 0; }
#searchRows tr.mediarow .thumb { width: 72px; height: 48px; border-radius: var(--round); display: block; }
#searchRows tr.mediarow td.name { width: 100%; }
#searchRows .svctitle { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 var(--gap-2); min-width: 0; }
#searchRows .svctitle > a { font-weight: 600; overflow-wrap: break-word; min-width: 0; }
#searchRows .svctype { color: var(--muted); font-size: var(--t-small); }
#searchRows .svcmeta {
  display: flex; flex-wrap: wrap; align-items: center; margin-top: 4px; color: var(--muted); font-size: 12.5px;
}
#searchRows .svcmeta .sep { margin: 0 8px; color: var(--faint); }
#searchRows .svcmeta .pill {
  background: none; border: 0; padding: 0; min-height: 0; font-size: 12.5px; color: var(--muted); font-weight: 500;
}
#searchRows .svcmeta .pill.p-stopped { color: var(--warn); }
@media (max-width: 480px) { #searchRows td.thumbcell { display: none; } }
@media (max-width: 760px) { #globalSearch input { width: 150px; max-width: 40vw; } }
#svcFacts code.itemid { font-size: 12px; user-select: all; overflow-wrap: normal; } /* 32 unbroken characters set a narrow column's width: 386 in a 375 window on CI (2026-10-18) */

/* ------------------------------------------------------------- two-step sign-in (ADR-201)
   <b>One look for the three places it is set up</b> — the sign-in card, My profile, and the requirement on
   Organization › Settings › Security. Base rules are the phone; nothing here needs a wider screen to fit. The key and
   the codes are set in the mono face at body size, on the quiet ground `code` already uses, because they are read
   character by character and typed into another device. Full hairline borders only. */
.sfstep-title { margin: var(--gap-4) 0 var(--gap-2); font-size: 16px; font-weight: 600; color: var(--ink); }
.sfstep-title:focus { outline: none; }
.sflede { margin: 0 0 var(--gap-3); font-size: var(--t-body); color: var(--ink); }
ol.sfsteps { list-style: none; margin: 0; padding: 0; }
ol.sfsteps > li > b { display: block; font-weight: 600; color: var(--ink); }
ol.sfsteps .lede { display: block; margin: 2px 0 var(--gap-2); font-size: var(--t-small); }
a.sfopen {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--gap-4); margin: var(--gap-1) 0 var(--gap-2);
  border: 1px solid var(--control-edge); border-radius: var(--round); background: var(--surface);
  color: var(--link); font-weight: 600; font-size: var(--t-small); text-decoration: none;
}
a.sfopen:hover { background: var(--surface-2); text-decoration: underline; }
a.sfopen:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sfkey {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2) var(--gap-3);
  padding: var(--gap-3); margin: 0 0 var(--gap-2);
  border: 1px solid var(--rule-strong); border-radius: var(--round); background: var(--surface-2);
}
.sfkey code {
  flex: 1 1 100%; min-width: 0; padding: 0; background: none; color: var(--ink);
  font-size: 16px; line-height: 1.6; letter-spacing: .06em; word-spacing: .2em;
}
.sfkey code span { display: inline-block; white-space: nowrap; }
.sfkey button, .sfrow button { min-height: 32px; }
.sfsaid:empty { display: none; }
ol.sfcodes {
  list-style: none; margin: 0 0 var(--gap-3); padding: var(--gap-3) var(--gap-4);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap-2) var(--gap-4);
  border: 1px solid var(--rule-strong); border-radius: var(--round); background: var(--surface-2);
}
ol.sfcodes code {
  padding: 0; background: none; color: var(--ink); font-size: 15px; letter-spacing: .03em;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.sfrow, .sfactions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2); }
.sfactions { margin-top: var(--gap-3); }
label.sfsaved {
  display: flex; align-items: flex-start; gap: var(--gap-2); margin: var(--gap-4) 0 0;
  font-size: var(--t-body); color: var(--ink); cursor: pointer;
}
label.sfsaved input { width: 18px; height: 18px; margin: 2px 0 0; flex: none; accent-color: var(--accent); }

/* The sign-in card: its own rules make every label a muted caption and every input full width. */
#signin label.sfsaved { display: flex; margin: var(--gap-4) 0 0; color: var(--ink); font-size: var(--t-body); }
#signin label.sfsaved input { width: 18px; }
#signin #signinSetupCode, #signin #signinCode {
  font-size: 18px; min-height: 44px; padding: 8px 10px; letter-spacing: .12em; font-variant-numeric: tabular-nums;
}
#signin #signinSetupCode + .hint, #signin #signinCodeHint { margin: var(--gap-1) 0 0; }
#signin #signinSetupError { margin-top: var(--gap-3); }
#signin .sflede { font-size: var(--t-small); }
#signin .sfstep-title { margin-top: var(--gap-3); }

/* My profile › Security. */
#view-user .profilesec { margin-top: var(--gap-5); padding-top: var(--gap-4); border-top: 1px solid var(--rule); }
#view-user .profilesec > h3 { margin: 0 0 var(--gap-3); font-size: 16px; }
#view-user .profilesec > h3:focus { outline: none; }
#view-user .profilesec label:not(.sfsaved) { display: block; font-weight: 600; font-size: 14px; margin: var(--gap-3) 0 var(--gap-1); }
#view-user .profilesec p { margin: 0 0 var(--gap-2); }
#view-user .profilesec .consequence { margin: var(--gap-3) 0 0; }
#view-user .profilesec .bad-inline { margin: var(--gap-2) 0 0; }
#pfSfCode { width: 100%; max-width: 16rem; min-height: 44px; font-size: 16px; letter-spacing: .08em; font-variant-numeric: tabular-nums; }
.sfhead { display: flex; align-items: center; gap: var(--gap-2); margin: 0 0 var(--gap-2); }
.sfhead b { font-weight: 600; }
.sfdone:empty { display: none; }
.sfdone { display: block; margin: var(--gap-3) 0 0; color: var(--ink); }

/* Organization › Settings › Security: the requirement. */
#sfRequireCard .consequence { margin: var(--gap-2) 0 0; max-width: 46rem; }
#sfRequireCard .consequence ul { margin: var(--gap-2) 0 0; padding-left: 1.2em; }
#sfRequireCard .consequence li + li { margin-top: var(--gap-1); }
#sfRequireCard fieldset.pickset { max-width: 36rem; }
#sfAsked { margin-top: var(--gap-5); padding-top: var(--gap-4); border-top: 1px solid var(--rule); }
#sfAsked h4 { margin-bottom: var(--gap-2); }
ul.sfasked { list-style: none; margin: var(--gap-2) 0 0; padding: 0; columns: 12rem 3; column-gap: var(--gap-5); }
ul.sfasked li { padding: 3px 0; break-inside: avoid; overflow-wrap: anywhere; }
#sfRequireDialog { width: min(560px, calc(100vw - 32px)); }
#sfRequireDialogBody .consequence { margin: var(--gap-3) 0; }

/* The administrator's notice: one line over the page, amber because it is a risk rather than a fault. */
.factornotice {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--gap-2) var(--gap-3);
  margin: 0 0 var(--gap-4); padding: var(--gap-2) var(--gap-3);
  border: 1px solid var(--warn-line); border-radius: var(--round); background: var(--warn-soft);
  color: var(--ink); font-size: var(--t-small);
}
.factornotice > .ico { width: 18px; height: 18px; flex: none; color: var(--warn); }
.factornotice > p { flex: 1 1 16rem; margin: 0; }
.factornotice > a { font-weight: 600; color: var(--link); text-decoration: underline; text-underline-offset: 2px;
                    min-height: 24px; display: inline-flex; align-items: center; }
.factornotice > button { min-height: 32px; color: var(--ink); }
#app.symfull > .factornotice { display: none; }
/* The two steps of a setup are numbered alike: the first is a list item, the second is the code field's label. */
.sfnum {
  display: inline-grid; place-items: center; width: 22px; height: 22px; margin-right: var(--gap-2); flex: none;
  border-radius: 50%; background: var(--accent-soft); color: var(--accent-ink); border: 1px solid var(--accent-line);
  font-size: 12px; font-weight: 700; vertical-align: 1px;
}
ol.sfsteps > li > b, label.sfsteplabel { display: flex; align-items: center; }
#signin label.sfsteplabel, #view-user .profilesec label.sfsteplabel {
  display: flex; margin: var(--gap-4) 0 var(--gap-2); color: var(--ink); font-size: var(--t-body); font-weight: 600;
}
/* The step's body hangs under its title, past the number: padding on the item, the title pulled back over it. */
ol.sfsteps > li { padding-left: 30px; }
ol.sfsteps > li > b { margin-left: -30px; }
/* A pointer that can hover is a computer, where the app link usually does nothing: a quiet link, and the key leads. */
@media (hover: hover) and (pointer: fine) {
  a.sfopen { min-height: 24px; padding: 0; border: 0; background: none; text-decoration: underline;
             text-underline-offset: 2px; font-weight: 500; }
  a.sfopen:hover { background: none; }
}
#view-user .profilesec p.sfafter { margin-top: var(--gap-3); }

/* The key's QR code — owner decision 2026-10-19. Black on white in either theme, since that is what a camera reads;
   hidden where the pointer is coarse, because a phone cannot scan its own screen and its link opens the app. */
.sfqr { margin: 10px 0 4px; padding: 0; line-height: 0; }
.sfqr svg { display: block; max-width: 100%; height: auto; border-radius: 4px; }
.sfqr[hidden] + .sfqr-said { display: none; }
@media (pointer: coarse) {
  .sfqr, .sfqr-said { display: none; }
}

/* ---------------------------------------------------- tables in a service (ADR-213)
   A table sits under the layers, under its own heading, as Portal's item page lists it. The heading is a row of
   the same table so the ids stay in one column; it reads as a caption, not as a data row. */
#serviceLayerRows tr.lsection th {
  padding: var(--gap-4) var(--gap-4) var(--gap-1); text-align: left; font-size: var(--t-small);
  font-weight: 600; color: var(--muted); background: transparent; border-bottom: 1px solid var(--rule);
}
#serviceLayerRows .tableicon { margin: 0 0 0 var(--gap-1); }
#serviceLayerRows td.empty.tablesonly { color: var(--muted); }
/* Server › service › Layers: the tables after the layers, under a heading of the same weight as the section's own. */
#page-layers h3.srvtables { margin: var(--gap-5) 0 var(--gap-1); font-size: var(--t-small); font-weight: 600;
  color: var(--ink); text-transform: none; letter-spacing: 0; }
#serviceVis .vistables { margin: 0 0 var(--gap-3); }
/* Settings › Feature layer: the tables after the layers, under a caption that reads as one, not as an eleventh-size label. */
#featureLayers h5.tablesub { margin: var(--gap-5) 0 0; font-size: var(--t-small); font-weight: 600; color: var(--muted);
  text-transform: none; letter-spacing: 0; }
/* A link inside a sentence is underlined, as every other in-text link on this page is (axe link-in-text-block). */
#visTables a, #serviceLayerRows td.tablesonly a { text-decoration: underline; text-underline-offset: 2px; }

/* Publish › tables (ADR-213 condition 5). A table in Databases and in Contents carries the console's table
   glyph where a layer carries its shape; in Contents it stands in the tick's column, after a gap the width of
   the twist, so every name starts at one x. The glyph is the muted ink, not the accent: a layer's shape is
   coloured because it previews a symbol, and a table has none. */
.pubtableglyph { display: inline-flex; flex: none; color: var(--muted); }
.pubtableglyph svg { width: 14px; height: 14px; }
.pubtableglyph.no { color: var(--faint); }
.pubtable .pubtableglyph { margin-left: -2px; }
.pubtwistgap { flex: none; width: 14px; }
/* The Tables heading is a caption inside the tree, not a row: no hover, no focus, no drop target. */
.pubsection { display: flex; align-items: baseline; gap: 8px; margin: 12px 8px 3px; padding: 6px 8px 4px;
  border-top: 1px solid var(--rule); font-size: var(--t-small); font-weight: 600; color: var(--muted); }
.pubsection .val { font-weight: 400; font-size: var(--t-tiny); color: var(--faint); }

/* ---------------------------------------------------------------- organizations (ADR-204) */

/* Which organization this console is, first in the reader's line, in the ink rather than the muted grey around it:
   it is the fact that tells two tabs apart. Shown only where the server holds more than one. */
#utilbar > #tenantName { order: -2; display: inline-flex; align-items: center; gap: 6px; min-width: 0;
                          max-width: 32ch; color: var(--ink); }
#utilbar > #tenantName[hidden] { display: none; }
#utilbar > #tenantName b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
#utilbar > #tenantName svg { width: 14px; height: 14px; flex: none; color: var(--accent-ink); }
@media (max-width: 760px) { #utilbar > #tenantName { max-width: 40vw; } }

/* A member of another organization: information, not a warning, so the accent's tone rather than the amber one. */
.factornotice.tenantnotice { border-color: var(--accent-line); background: var(--accent-soft); }
.factornotice.tenantnotice > .ico { color: var(--accent-ink); }

/* The list's verbs sit on one line where there is room and wrap where there is not. */
#tnRows td.tnacts { text-align: right; }
#tnRows td.tnacts button + button { margin-left: var(--gap-2); }
#tnRows td.name .rowmeta code { font-size: .95em; }
#tnNote { margin-top: var(--gap-3); }

/* The dialogs' own labels, and a read-only value beside the button that copies it. */
.tnlabel { display: block; font-weight: 600; margin: var(--gap-4) 0 6px; }
.copyrow { display: flex; gap: var(--gap-2); align-items: stretch; }
.copyrow > input { flex: 1 1 auto; min-width: 0; font-family: var(--mono); }
.copyrow > button { flex: none; }
#tnIssuedOnce { margin-top: 0; }
#tnRemove .tnholds { color: var(--alert); font-weight: 600; margin-top: 0; }
#tnPurgeKey { width: 100%; font-family: var(--mono); }
#tnDialog .picker > div[hidden], #tnDialog .picker > p[hidden] { display: none; }
#tnDialog .picker input, #tnDialog .picker textarea { width: 100%; }
#tnDialog .picker > label, #tnDialog .picker > div > label { margin-top: var(--gap-3); }
#tnDialog .picker > label:first-child { margin-top: 0; }

/* Settings › Other organizations: its own card between the settings Save sends and deletion, because it writes on its
   own as the deletion lock does. */
.gsform.tenantcard { margin-top: var(--gap-5); }
.gsform.tenantcard .dangerswitch { margin: var(--gap-2) 0 var(--gap-3); }
.gsform.tenantcard > .hint { margin: 0 0 var(--gap-2); }

/* ADR-204 §5e item 16: the way to a group's Settings, from the member picker, looks like the link it is. */
.picker .hint a { text-decoration: underline; text-underline-offset: 2px; }
