/* Dragon Websites — interface styles.

   This file is Dragon Command Center's app.css, carried across so the two products are the
   same product to look at. Keep it that way: when one changes, copy it rather than editing
   one of the two copies, and put anything only this app needs in the section at the end.
   Spacing is deliberately generous: elements never touch or crowd, and vertical
   margins are symmetric so sections read as evenly separated blocks. */

/* The interface is dark, and the browser needs telling. Without this it styles native
   controls -- checkboxes, the tier select, date pickers, scrollbars -- for a light page,
   so an unchecked checkbox rendered as a bright white square on a near-black table. */
:root {
  color-scheme: dark;
}

:root {
  --bg: #0f1216;
  --surface: #171b21;
  --surface-raised: #1e242c;
  /* A step below the surface, for the areas that frame something rather than hold it: a
     dialog's header and footer. Darker than the body they sit against, which is what makes
     three areas read as three. */
  --surface-sunken: #12161b;
  --border: #2a323c;
  --text: #e6eaef;
  /* Lightened from #9aa5b1, which read at 6.91:1 on the surface and 6.24:1 on a raised
     card -- under the 7:1 that AAA asks of body text, on the color most of this product's
     prose is set in. This clears it on every surface here: 8.12:1 on the surface, 7.34:1
     on a card, 8.53:1 in a dialog's header. */
  --text-muted: #a8b3bf;
  /* The arc for items nobody has taken up: the muted text color, softened, so it reads as
     the quiet part of a ring rather than as a fourth state with something to say. */
  --donut-idle: rgba(154, 165, 177, 0.4);
  /* Orange, in two jobs that pull opposite ways.
     As text on a dark page it has to be light enough to read against it; as a fill under
     a white label it has to be dark enough for the label to be read against it. One
     value cannot do both: the old single orange gave its buttons a label at 4.05:1,
     under the 4.5:1 the rule asks for, while reading at 4.26:1 on a card, also under it.
     So there are two, each measured for the job it does, and each with a hover a step
     lighter that stays over the line. */
  --accent: #e06529;
  --accent-hover: #f0935a;
  --accent-fill: #a94519;
  --accent-fill-hover: #c4501d;
  --ok: #3fa66a;
  --warn: #d19a3c;
  /* Red as text: pills, alerts, the strength meter's first step. Lightened for the same
     reason the orange was -- it read at 3.92:1 on a card, which is not a reading. */
  --danger: #e06b6b;
  --danger-hover: #f09a9a;
  /* The red a destructive button is filled with.
     --danger itself is tuned for text and pills on the dark surface, which puts it at
     much the same lightness as the accent orange -- so a Revoke button and a Save button
     read as the same button at a glance down a table. This one is darker and further from
     the orange, which is the whole job of the color on a control that cannot be undone. */
  --danger-strong: #9e2b2b;
  --danger-strong-hover: #c23333;

  /* Password strength, one step per requirement met. Chosen for even perceived
     spacing on the dark surface rather than by rotating hue mechanically. */
  --strength-1: #cf4b4b;
  --strength-2: #d97036;
  --strength-3: #d19a3c;
  --strength-4: #a8ac41;
  --strength-5: #3fa66a;

  --space-xs: 0.5rem;
  --space-sm: 0.875rem;
  --space-md: 1.5rem;
  --space-lg: 2.5rem;
  --space-xl: 4rem;

  /* How wide a switch is. */
  --toggle-width: 34px;

  /* The height of the strip that closes the page, under the navigation and under the
     content alike.
     The rule above the footer and the rule under the account block read as one line
     drawn across the page, interrupted by the column edge. Where each sits is decided
     by the height of what is beneath it, and those two heights used to be arrived at
     separately -- one from padding and a line of small text, the other from padding and
     a name -- which put the navigation's rule eight pixels above the footer's. Both
     blocks are this tall now, so the line meets itself. */
  --page-foot: 4.5rem;

  --radius: 10px;
  --measure: 68rem;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* Nothing scrolls sideways. */
body { overflow-x: hidden; }

a { color: var(--accent); text-decoration: none; }
/* A link lightens as well as underlining. The underline alone is easy to miss on a line
   that already sits among other text, and the lighter orange reads at a higher contrast
   than the resting one rather than a lower one. */
a:hover { color: var(--accent-hover); text-decoration: underline; }

/* ---------------------------------------------------------------------------
   Hovering fades rather than snaps.

   Half a second, and in both directions: the transition is declared on the
   resting state, not on :hover, so it governs the pointer arriving and leaving
   alike. Declared on :hover it would ease in and snap back.

   Only what actually changes under the pointer is listed. Transitioning
   everything would catch layout properties as well and make a hovered row
   appear to resize itself.

   Two things are deliberately left out. The sub-navigation reveal has its own
   timing, chosen separately. And the toggle switch keeps its 120ms: that one is
   a control changing state rather than a hover, and half a second of travel
   after a click reads as the switch not having registered it.
   --------------------------------------------------------------------------- */
a,
button,
.button,
.sidenav-toggle,
.icon-button,
.account-tab,
.stat-toggle,
th.is-sortable,
tbody tr,
.picker-button,
.picker-row {
  transition:
    background-color 500ms ease,
    color 500ms ease,
    border-color 500ms ease;
}

@media (prefers-reduced-motion: reduce) {
  a,
  button,
  .button,
  .sidenav-toggle,
  .icon-button,
  .account-tab,
  .stat-toggle,
  th.is-sortable,
  tbody tr,
  .picker-button,
  .picker-row {
    transition: none;
  }
}

/* ---------------------------------------------------------------------------
   The navigation column.

   Down the left rather than across the top. A row runs out of width as it grows
   -- it already wrapped onto a second line on a narrow window -- and a row has
   nowhere at all to put an item nested under another. A column grows downward,
   which is the direction there is always more of, and nesting is just an indent.
   --------------------------------------------------------------------------- */

body.has-sidenav {
  display: grid;
  grid-template-columns: 15rem 1fr;
  min-height: 100vh;
}

/* Without this the content column refuses to be narrower than its widest child,
   and a wide table pushes the whole grid sideways instead of scrolling inside
   its own container. A grid child's default minimum is its content, not zero. */
.shell { min-width: 0; display: flex; flex-direction: column; }
.shell > main { flex: 1 0 auto; }

.sidenav {
  background: var(--surface);
  /* The accent edge belongs to the column itself rather than to the admin area. It
     used to mark the platform-administration side alone; the two navigations are meant
     to look the same, so it is the default and there is no second version of it. */
  border-right: 1px solid var(--accent);
  /* The column stays put while the page scrolls: with a dozen destinations, having
     to scroll back to the top to reach one is the thing a sidebar is meant to fix. */
  position: sticky;
  top: 0;
  align-self: start;
  /* A full viewport height, not merely a cap: the account block is pinned to the
     bottom of the column, and against a content-height column it sat directly under
     the last link with the page background showing beneath it. */
  height: 100vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-md) 0;
}


.sidenav-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: 0 var(--space-md);
}

.sidenav-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: var(--space-md);
  min-height: 0;
}

.sidenav-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.sidenav-list a {
  display: block;
  padding: var(--space-xs) var(--space-md);
  color: var(--text-muted);
  border-left: 2px solid transparent;
  font-size: 0.92rem;
}

.sidenav-list a:hover {
  color: var(--text);
  background: var(--surface-raised);
  text-decoration: none;
}

/* Where you are, and where you are inside. The second matters once items nest:
   a marked child with an unmarked parent reads as two unrelated things. */
.sidenav-list a.is-current {
  color: var(--text);
  background: var(--surface-raised);
  border-left-color: var(--accent);
  font-weight: 600;
}
.sidenav-list a.is-within { color: var(--text); }

/* A nested list is indented and quieter, so the hierarchy is legible without a
   second mechanism. */
.sidenav-list.is-sub {
  margin: 0;
}
.sidenav-list.is-sub a {
  padding-left: calc(var(--space-md) + var(--space-md));
  font-size: 0.86rem;
}

/* Sub-items appear on hover, and on keyboard focus so they are not mouse-only.

   The open and closed states are a single grid row of 0fr and 1fr, which interpolates to
   whatever height the list actually is. The obvious alternative, animating max-height
   between zero and a number large enough for any list, is what this replaced: against a
   list 76px tall that number had to be 384px, so opening reached full height in a fifth
   of the duration and closing spent four fifths of it moving invisibly back down before
   anything appeared to happen. It read as a snap with a delay in front of it, which is
   worse than no animation at all.

   An item with no children carries no .has-sub and is unaffected, which is what lets
   this ship before there is a single sub-item to show. */
.sidenav-sub {
  display: grid;
  grid-template-rows: 0fr;
  transition:
    grid-template-rows 300ms cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity 220ms ease;
  opacity: 0;
}

/* The row is what collapses, so the list inside it must be allowed to be shorter than
   its content and must not spill while it is. */
.sidenav-sub > .sidenav-list {
  min-height: 0;
  overflow: hidden;
}

.sidenav-list .has-sub:hover > .sidenav-sub,
.sidenav-list .has-sub:focus-within > .sidenav-sub {
  grid-template-rows: 1fr;
  opacity: 1;
}

/* The section you are actually in stays open, so the marked child is not hidden the
   moment the pointer leaves. */
.sidenav-list .has-sub:has(a.is-current) > .sidenav-sub {
  grid-template-rows: 1fr;
  opacity: 1;
}

/* Motion is decoration here: the list opens either way, and someone who has asked their
   system for less of it should not be made to wait through a curve. */
@media (prefers-reduced-motion: reduce) {
  .sidenav-sub { transition: none; }
}

/* Who you are sits at the bottom of the column, away from the destinations, and opens
   the same way any other section does. Its own sub-items -- Profile and Sign Out -- are
   revealed on hover rather than standing permanently at the foot of every page. */
/* The count beside a name in the column.
   The only thing that tells somebody an alert exists without their opening the page, so
   it is legible rather than decorative: the tone the pills use for something wrong, and
   room around the figure so a three-digit count does not touch the word beside it. */
.nav-count {
  display: inline-block;
  margin-left: var(--space-xs);
  padding: 0 0.4rem;
  border-radius: 999px;
  background: var(--danger-strong);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 650;
  line-height: 1.5;
}

.sidenav-account {
  margin-top: auto;
  /* The column's own bottom padding would sit beneath this block and lift its rule off
     the footer's line, so the block runs to the bottom edge of the column instead and
     takes the closing strip's height itself. */
  margin-bottom: calc(var(--space-md) * -1);
  min-height: var(--page-foot);
  border-top: 1px solid var(--border);
  /* Centered in the strip rather than sitting at the top of it, which puts the name on
     the same row as the footer's own line across the way. Padding rather than none, so
     that the sub-items revealed on hover do not touch the rule when the block grows. */
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--space-sm) 0;
}

/* The name is one line and truncated rather than broken wherever it reaches the edge.
   A long display name split across two lines reads as two names. */
.sidenav-account > .sidenav-list > li > a {
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Only ever visible on a narrow screen, where the column becomes a bar. */
/* A mark rather than a box. Every other icon in this product is drawn without edges, and
   a bordered rectangle in the corner of a phone screen read as a button somebody had
   already pressed. The tap target keeps its size through the padding. */
.sidenav-toggle {
  display: none;
  background: none;
  border: none;
  color: var(--text);
  padding: 0.25rem 0.6rem;
  font-size: 1rem;
  line-height: 1;
}
/* The button rule draws a hairline inside every button on hover, which on a mark with no
   fill and no label reads as a box appearing around it -- the same reason the icon buttons
   turn it off. */
.sidenav-toggle:hover {
  background: none;
  color: var(--accent);
  box-shadow: none;
}

/* Keyboard users get past the whole column in one keystroke. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  background: var(--surface-raised);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--accent);
  border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus { left: 0; }

/* Two lines, always: "Dragon" over what it is. Left to wrap on its own it broke
   wherever the column happened to run out -- "Dragon Command" over "Center" -- which
   splits the product's name across the break instead of at it. */
.wordmark {
  display: block;
  font-size: 1.05rem;
  font-weight: 650;
  letter-spacing: 0.01em;
  line-height: 1.25;
  color: var(--text);
}
.wordmark span {
  display: block;
  color: var(--accent);
}
/* The wordmark is a brand mark rather than a link in a sentence, so it does not take
   the underline that the global a:hover rule gives every other link. */
.wordmark:hover { text-decoration: none; }

main {
  max-width: var(--measure);
  margin: 0 auto;
  width: 100%;
  /* Symmetric top and bottom. */
  padding: var(--space-lg) var(--space-md);
}

h1 {
  font-size: 1.6rem;
  font-weight: 650;
  margin: 0 0 var(--space-xs);
  letter-spacing: -0.01em;
}

h2 {
  font-size: 1.15rem;
  font-weight: 620;
  margin: 0 0 var(--space-sm);
}

.lede {
  color: var(--text-muted);
  margin: 0 0 var(--space-lg);
  max-width: 46rem;
}

/* The measure exists so a paragraph of prose is comfortable to read. Above a table that
   runs the width of the page, a paragraph stopping two thirds of the way across reads as
   a ragged edge rather than as a considered line length. */
.lede.wide { max-width: none; }

section { margin: 0 0 var(--space-xl); }
section:last-of-type { margin-bottom: 0; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-md);
}

.card + .card { margin-top: var(--space-md); }

/* The last thing in a card sits as far from the bottom edge as the first sits from the
   top, and no farther.
   Two things had been adding to the card's own padding rather than sitting inside it: a
   field's bottom margin, which is spacing between fields and belongs between them, and
   the bottom padding of a table's final row, which is the table's internal rhythm. Both
   read as a band of empty ground under the last item -- forty pixels of it under the
   time zone -- and neither is anything anyone asked for. */
.card > *:last-child { margin-bottom: 0; }
/* Named rather than left to the rule above, which a later `.field:last-of-type` outweighs
   at equal specificity. A field's generous bottom margin is spacing before the next field
   and has nothing to sit before when it is the last thing in the card. */
.card > .field:last-child { margin-bottom: 0; }
.card > table:last-child tbody tr:last-child > td,
.card > .table-wrap:last-child tbody tr:last-child > td {
  padding-bottom: 0;
}

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-sm);
  flex-wrap: wrap;
}

/* Tables size to their content and scroll inside their own container rather than
   pushing the page sideways. */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  /* The ground anything drawn over a table is placed against -- today the line that shows
     where a dragged row would land. */
  position: relative;
}

table {
  border-collapse: collapse;
  width: auto;
  min-width: 100%;
}

th, td {
  text-align: left;
  /* Cells align to the top. A row whose first cell runs to several lines -- a setting with a
     list of domains under it -- would otherwise float its status and controls to the middle
     of that height, leaving them out of line with the single-line rows above and below. */
  vertical-align: top;
  padding: var(--space-sm) var(--space-md);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

th {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
  background: var(--surface-raised);
}

/* A table whose rows are all one line high, where the tallest thing in a row is a
   control rather than text. Top alignment is the default because a cell that wraps to
   several lines would otherwise drag its row's status and buttons to the middle of that
   height; where nothing wraps there is no such height, and the words simply need to sit
   across from the button rather than above it. */
table.middle-data th,
table.middle-data td {
  vertical-align: middle;
}

tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--surface-raised); }

td.wrap { white-space: normal; min-width: 16rem; }

/* For values that are long but should not claim a wide column of their own.
   overflow-wrap breaks only where it must, so an email wraps at a sensible point
   instead of one character per line. */
td.wrap-tight {
  white-space: normal;
  overflow-wrap: break-word;
  max-width: 20rem;
  min-width: 9rem;
}

.pill {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.pill-active  { background: rgba(63, 166, 106, 0.16); color: var(--ok); }
.pill-failing { background: rgba(207, 75, 75, 0.16);  color: var(--danger); }
.pill-revoked { background: rgba(154, 165, 177, 0.16); color: var(--text-muted); }
/* Partly configured: some items done, some not. Amber reads as "attention", not "broken". */
.pill-partial { background: rgba(214, 158, 46, 0.16); color: var(--warn, #d69e2e); }
.pill-stale   { background: rgba(209, 154, 60, 0.16); color: var(--warn); }
/* The standard rate: a state like the other two beside it, and the quiet one of the
   three, because it is what most rows say and nothing about it needs attention. */
.pill-standard { background: rgba(154, 165, 177, 0.16); color: var(--text-muted); }

.muted { color: var(--text-muted); }

/* A paragraph set as a block: both edges even, the way the sentences before a deletion
   are set. For prose that states an arrangement rather than describing the page -- the
   terms an account is held on. Words are never broken across lines to achieve it. */
.justified {
  text-align: justify;
  hyphens: manual;
}
.empty {
  color: var(--text-muted);
  padding: var(--space-lg);
  text-align: center;
}

/* Forms */
form { margin: 0; }

.field { margin-bottom: var(--space-md); }
.field:last-of-type { margin-bottom: var(--space-lg); }

/* A card whose last row of fields is its last thing at all.
   The trailing margin above is room for whatever follows the fields -- a button, another
   row -- and where nothing follows, it leaves the card sitting lower on the bottom than
   on the top. */
.card-tight .field:last-of-type { margin-bottom: 0; }

label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: var(--space-xs);
}

.hint {
  display: block;
  font-size: 0.82rem;
  font-weight: 400;
  color: var(--text-muted);
  margin-top: 0.35rem;
}

/* A note that belongs to the whole form rather than to one field, standing between the
   fields and the button that acts on them. It takes the same space under it that a row of
   fields takes, so the button is not pressed against the sentence explaining what it does. */
form > .hint {
  margin-bottom: var(--space-lg);
}

input, select {
  width: 100%;
  padding: 0.7rem 0.85rem;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  font: inherit;
  font-size: 0.95rem;
}

input:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(212, 87, 31, 0.18);
}

/* The browser's own arrow is drawn wherever the platform likes, which on this theme
   left it almost touching the right border and no amount of padding moved it reliably.
   Drawing our own puts it at a distance we choose, identically in every browser. The
   chevron is an inline data URI rather than a file so it cannot 404 and costs no
   request. */
select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%239aa5b1' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.9rem center;
  background-size: 0.7rem;
  /* Room for the chevron and the gap either side of it, so a long option never runs
     underneath it. */
  padding-right: 2.4rem;
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-md);
  /* Fields align at the top, so a hint under one input does not shift its neighbors. */
  align-items: start;
}

button, .button {
  display: inline-block;
  padding: 0.65rem 1.3rem;
  border: 1px solid transparent;
  border-radius: 8px;
  background: var(--accent-fill);
  color: #fff;
  font: inherit;
  font-weight: 600;
  font-size: 0.92rem;
  cursor: pointer;
}
/* Hover lightens the fill and adds a hairline inside the edge. The fill alone can only
   move so far before the white label stops being readable against it, and the ring costs
   no contrast at all -- so the two together are what makes the change plain. */
button:hover, .button:hover {
  background: var(--accent-fill-hover);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
  /* A link styled as a button is still a link, so the rule that lightens link text on
     hover reaches it and turned its label orange on an orange fill. The label is white
     because it is read against that fill; hovering does not change what it is read
     against. */
  color: #fff;
  text-decoration: none;
}

/* The secondary treatment applies to links styled as buttons too. Without the .button
   selector here, an <a class="button secondary"> fell back to the filled accent style and
   shouted louder than the real action beside it. */
button.secondary, .button.secondary {
  background: transparent;
  border-color: var(--border);
  color: var(--text);
}
button.secondary:hover, .button.secondary:hover { background: var(--surface-raised); }

/* An action in a table row stays on one line: a wrapped label pushes the info icon onto a
   line of its own and makes every row taller than it needs to be. */
/* Buttons standing side by side under a table. Only a table cell's row of actions was ever
   given a gap, so a pair outside one sat with nothing between them but the space in the
   markup -- which collapses to a single character's width. */
div.row-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-md);
}

td.row-actions .button.small,
td.row-actions button.small {
  white-space: nowrap;
}

button.danger, .button.danger { background: var(--danger-strong); }
button.danger:hover, .button.danger:hover { background: var(--danger-strong-hover); }

button.link {
  background: none;
  border: none;
  color: var(--danger);
  padding: 0;
  font-size: 0.88rem;
  text-decoration: underline;
}
/* Text rather than a fill, so it lightens the way a link does -- in its own color, since
   a red control that turned orange on hover would read as a different control. */
button.link:hover { background: none; color: var(--danger-hover); }

/* The small treatment applies to links styled as buttons too, for the same reason the
   secondary treatment above does: an <a class="button small"> matched neither the padding
   nor the font size, so it sat a head taller than the real button beside it while the
   markup on both said "small". A class that silently does nothing on one of the two ways
   a button is written is worse than no class at all. */
button.small, .button.small {
  padding: 0.35rem 0.8rem;
  font-size: 0.82rem;
}

button:disabled { opacity: 0.55; cursor: not-allowed; }

/* The same room above as below.
   
   It had a bottom margin and no top one, so the box sat against whatever came before it
   and a comfortable distance from what came after. Symmetric values alone were not
   enough: vertical margins between siblings collapse to the larger of the two, so a
   24px top margin under a paragraph carrying 40px still rendered 40 above and 24 below.
   Matching the largest margin a neighbor is likely to bring makes both sides settle at
   the same number. */
.notice {
  padding: var(--space-sm) var(--space-md);
  border-radius: 8px;
  margin: var(--space-lg) 0;
  font-size: 0.92rem;
  border: 1px solid transparent;
}
.notice-error { background: rgba(207, 75, 75, 0.12); border-color: rgba(207,75,75,0.4); color: #f0b4b4; }
.notice-ok    { background: rgba(63, 166, 106, 0.12); border-color: rgba(63,166,106,0.4); color: #a8ddc0; }
.notice[hidden] { display: none; }

.auth-panel {
  max-width: 26rem;
  margin: var(--space-xl) auto;
}

/* A heading with no paragraph under it carries the room that paragraph used to.
   The h1's own bottom margin is small because it almost always has a lede beneath
   it; where the lede is gone, that leaves the heading sitting against the card. */
.auth-panel h1:not(:has(+ .lede)) {
  margin-bottom: var(--space-lg);
}

.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--space-md);
}

/* A second row of tiles under the first. The gap between the rows is the gap between the
   tiles, so a set split over two lines reads as one block rather than two sections. */
.stat-row-next {
  margin-top: var(--space-md);
}

/* Every tile on one line, however many there are.
   A client's dashboard is one answer about one client, and auto-fit would break the last
   tile or two onto a line of their own on a middling screen, which reads as a second set.
   Below the width where five tiles still have room to be read, it goes back to fitting as
   many as will fit. */
.stat-row-one {
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  grid-template-columns: none;
}

@media (max-width: 900px) {
  .stat-row-one {
    grid-auto-flow: row;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  }
}

.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-md);
  /* A tile is a figure and its name, one above the other, and nothing else shares the
     line with either. Centered, the two read as one object however wide the tile gets. */
  text-align: center;
  /* Held in the middle, so a tile with a ring in it and a tile with a figure in it carry
     what they hold the same way. Aligned to the top, the shorter contents sat high in a
     box that had grown for its neighbor and read as a tile with a gap under it. */
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.stat .value { font-size: 1.7rem; font-weight: 650; line-height: 1.2; }
.stat .label { color: var(--text-muted); font-size: 0.85rem; margin-top: 0.2rem; }

/* A kind's items as one ring.
   Every item of that kind is in exactly one arc -- paid for, carried free, or found and
   never taken up -- so the three are a whole, which is what a ring says and three numbers
   side by side do not. The two boundaries come from the server as percentages; the colors
   stay here. A kind with nothing in it sets both to zero and reads as an empty gray ring
   rather than as a full arc of something there is none of. */
.donut {
  position: relative;
  width: 4.5rem;
  height: 4.5rem;
  margin: 0 auto var(--space-sm);
  border-radius: 50%;
  background: conic-gradient(
    var(--ok) 0 var(--pro-end, 0%),
    var(--warn) var(--pro-end, 0%) var(--free-end, 0%),
    var(--donut-idle) var(--free-end, 0%) 100%
  );
}
/* The hole. Painted in the tile's own background rather than masked, so the ring looks
   the same in a browser without mask support. */
.donut::after {
  content: '';
  position: absolute;
  inset: 24%;
  border-radius: 50%;
  background: var(--surface);
}

/* A pie is the same circle with the hole filled in. Nothing else about it differs, which
   is why it is one class here rather than a second gradient to keep in step. */
.donut.is-pie::after {
  content: none;
}

/* The same three parts as bars, upright or lying down.
   Each bar is a share of the kind's total, not of the largest of the three: a full bar
   means "all of them" on every tile, where scaling to the largest would leave one bar
   full on every tile and make two tiles impossible to compare. The track behind each bar
   is the whole, so a short bar still shows what it is short of. */
.bars {
  display: flex;
  margin: 0 auto var(--space-sm);
}
.bar-track {
  background: rgba(154, 165, 177, 0.12);
  border-radius: 999px;
  overflow: hidden;
}
.bar-fill {
  display: block;
  border-radius: 999px;
}
.bar-fill.is-pro { background: var(--ok); }
.bar-fill.is-free { background: var(--warn); }
.bar-fill.is-idle { background: var(--donut-idle); }

/* One bar rather than three: the ring unrolled, read in the same order.
   The three segments are parts of one length, so the bar is always full and the eye
   compares them against each other rather than each against an empty track. The track
   itself only shows through when a kind has nothing in it at all. */
.stack {
  display: flex;
  overflow: hidden;
}
.stack .bar-fill {
  border-radius: 0;
}
.stack-vertical {
  flex-direction: column;
  width: 0.9rem;
  height: 4.5rem;
  margin: 0 auto var(--space-sm);
}
.stack-vertical .bar-fill {
  width: 100%;
  height: var(--share, 0%);
}
.stack-horizontal {
  width: 100%;
  height: 0.9rem;
}
.stack-horizontal .bar-fill {
  height: 100%;
  width: var(--share, 0%);
}
/* A single lying-down bar is much shorter than a ring, so it is centered in the height a
   ring would have taken. A tile keeps its proportions whichever shape is chosen. */
.stack-holder {
  display: flex;
  align-items: center;
  height: 4.5rem;
  margin-bottom: var(--space-sm);
}

/* Upright: three columns standing on one line, the height of the tallest ring they
   replace so a row of tiles keeps its height whichever shape is chosen. */
.bars-vertical {
  height: 4.5rem;
  gap: var(--space-sm);
  align-items: flex-end;
  justify-content: center;
}
.bars-vertical .bar-track {
  width: 0.85rem;
  height: 100%;
  display: flex;
  align-items: flex-end;
}
.bars-vertical .bar-fill {
  width: 100%;
  height: var(--share, 0%);
}

/* Lying down: three rows, read top to bottom in the same order the colors are given. */
.bars-horizontal {
  flex-direction: column;
  justify-content: center;
  gap: 0.55rem;
  width: 100%;
  height: 4.5rem;
}
.bars-horizontal .bar-track {
  width: 100%;
  height: 0.7rem;
}
.bars-horizontal .bar-fill {
  height: 100%;
  width: var(--share, 0%);
}

/* The same three counts under the ring, each in its arc's color and in its order. */
.donut-figures {
  font-size: 1.35rem;
  color: var(--text-muted);
}
.donut-figures .is-pro { color: var(--ok); }
.donut-figures .is-free { color: var(--warn); }
.donut-figures .is-idle { color: var(--text-muted); }
/* The word after a number in a tile or a cell -- "1 Free / 2 Pro". Quieter and smaller
   than the figure it labels, because the figures are what is compared down a column and
   the words say the same thing on every row. */
.unit {
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--text-muted);
  margin-left: 0.2rem;
}

/* Confirmation modal. Confirmations use this, never the browser's own dialogs. */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(8, 10, 13, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-md);
  z-index: 50;
}
.modal-backdrop[hidden] { display: none; }

/* Three areas, drawn as three areas: the padding belongs to each of them rather than to the
   dialog, so the rules between them run the whole width and a header reads as a header
   instead of as the first line of the body. */
.modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0;
  max-width: 30rem;
  width: 100%;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
  /* A column of three, so a dialog too tall for the window scrolls its body and keeps its
     title and its buttons where they are. The whole dialog used to scroll, which put the
     button that ends it below the bottom of the screen. */
  display: flex;
  flex-direction: column;
  max-height: 90vh;
}

/* The title, and at the far end of the same row the mark that explains the dialog. Centered
   against each other rather than sitting on a shared baseline: one is words and the other a
   circle, and a baseline would hang the circle low. */
.modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border);
  background: var(--surface-sunken);
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
}
/* The title carries no margin of its own here, and sits on the same line box as the mark
   beside it. Written against the header as well as the heading: the dialog's own heading
   rule further down sets a bottom margin, and an equal-specificity selector earlier in the
   file loses to it -- which is where the space under the title came from. */
.modal .modal-head h3 {
  margin: 0;
  line-height: 1.2;
}
/* Quiet until it is wanted. The mark is an offer, not an instruction, so it sits back until
   the pointer is on it -- written against the header as well as the mark, because every
   icon button is drawn in the accent color and a bare class does not outrank that. */
.modal-head .modal-info { color: var(--text-muted); }
.modal-head .modal-info:hover { color: var(--accent); }

.modal-body {
  padding: var(--space-lg);
  overflow-y: auto;
}
/* The body carries the space under its own last element, so a dialog does not end with a
   paragraph's margin pressed against the rule above the buttons. */
.modal-body > *:last-child { margin-bottom: 0; }

/* Every button a dialog has, in one place at the end of it. */
.modal-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
  border-top: 1px solid var(--border);
  background: var(--surface-sunken);
  border-radius: 0 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px);
}
/* The modal's headings and its section labels are one style: "Recommendation" and "Where To
   Change It" are headings of their sections, so they read like the title above them rather
   than as a different kind of thing. */
.modal h3,
.modal .modal-reco-label { margin: 0 0 var(--space-sm); font-size: 1.1rem; color: var(--text); }
/* One paragraph style throughout, in the muted tone. The description and the recommendation
   were two different colors, which made the same kind of prose look like two kinds; the muted
   tone is the quieter of the two and lets the headings carry the structure. */
/* Justified, as in the danger card this usually follows from: what is read last before
   something is deleted reaches both edges rather than ending each line wherever the words
   ran out. No hyphenation with it: a word is never broken across two lines here, so on a
   measure this narrow the word spacing carries the justification by itself. */
.modal p  {
  margin: 0 0 var(--space-lg);
  color: var(--text-muted);
  text-align: justify;
  hyphens: manual;
}



footer {
  border-top: 1px solid var(--border);
  margin-top: var(--space-xl);
  /* Height comes from --page-foot rather than from the padding, so that the rule above
     it lands on the same row as the one under the navigation. See that token. */
  min-height: var(--page-foot);
  padding: 0 var(--space-md);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* Below this there is not enough width for a column of navigation beside the
   content, so it becomes a bar across the top with the list folded away behind
   the toggle. The bar itself stays put; only the list collapses. */
@media (max-width: 900px) {
  body.has-sidenav { grid-template-columns: 1fr; }

  .sidenav {
    position: static;
    /* Released from the full viewport height it takes as a column. Left at 100vh the
       bar alone filled the screen and pushed every page's content below the fold. */
    height: auto;
    max-height: none;
    overflow: visible;
    border-right: none;
    border-bottom: 1px solid var(--accent);
    /* As a bar it carries the brand and the menu mark and nothing else, so it is spaced
       like a header: the same air under them as above. Zero here left the rule sitting
       directly under the wordmark. */
    padding-bottom: var(--space-md);
    gap: var(--space-sm);
  }

  .sidenav-toggle { display: inline-block; }

  /* Collapsed by the client script. It ships expanded so that a browser running no
     script shows the whole list rather than a button that does nothing. */
  .sidenav-body[hidden] { display: none; }
  .sidenav-body { padding-bottom: var(--space-md); }

  /* No column here, so no rule to line the footer up with: the account block is one more
     row in a bar and goes back to being only as tall as its contents. */
  .sidenav-account {
    margin-top: var(--space-sm);
    margin-bottom: 0;
    min-height: 0;
    padding-top: var(--space-sm);
  }

  /* There is no hover on a touch screen, so nothing would ever reveal a sub-item. The
     whole list is already behind the toggle here and vertical room is not the constraint
     it is in a column, so children are simply shown, with nothing to animate. */
  .sidenav-sub {
    grid-template-rows: 1fr;
    opacity: 1;
    transition: none;
  }
}

@media (max-width: 640px) {
  main { padding-left: var(--space-sm); padding-right: var(--space-sm); }
  .modal-footer { flex-direction: column-reverse; }
  .modal-footer button { width: 100%; }
}


/* Administration. Visually distinct so it is never ambiguous whose data is on screen:
   the wordmark in the navigation column reads "Dragon Administration" and the column
   itself is edged in the accent, which says it on every page without a bar of prose. */


/* Money reads down the column, so it is right-aligned and figures line up. Tabular
   numerals keep the digits on a shared grid; proportional ones make $9.99 and $11.00
   ragged against each other. */
td.amount, th.amount {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Everything but the name column centered. The name is what the table is read by, and a
   centered column of names has no edge to run the eye down. */
table.center-data th:not(:first-child),
table.center-data td:not(:first-child) {
  text-align: center;
}

/* Where the first column is a checkbox, the name is the second and the rule above would
   center it. The checkbox centers under its own heading; the cell after it goes left. */
table.center-data th.pick + th,
table.center-data td.pick + td {
  text-align: left;
}

/* An action column ends at the table's own edge.
   Left to itself the column takes whatever width is going and the buttons float
   somewhere inside it, so a row of them down the table has no shared edge to read
   against. Named explicitly under center-data as well, which would otherwise center
   every column but the first and outrank a plainer selector. */
th.actions-end,
td.actions-end,
table.center-data th.actions-end,
table.center-data td.actions-end {
  text-align: right;
}

/* A column of prose in an otherwise centered table.
   An address is read along its length, like a name, and centering leaves it with no edge
   to run the eye down -- the same reason the first column stays left. Named under
   center-data as well, which centers everything after the first and would otherwise
   outrank a plainer selector. */
th.align-start,
td.align-start,
table.center-data th.align-start,
table.center-data td.align-start {
  text-align: left;
}

/* A column no wider than what is in it.
   A width of 1% is the browser's way of being told "as narrow as the content allows": the
   table still has to reach the full width, so every spare pixel goes to the columns that
   can use it and this one closes up around its own text, at the far edge of the table.
   Nowrap keeps that closing from breaking a word across two lines. */
th.col-tight,
td.col-tight {
  width: 1%;
  white-space: nowrap;
}

/* Invoice numbers are identifiers, not prose: even width so they scan as a column. */
td.num {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The field inside a prompt modal. Caption above the input, both full width, with the
   same breathing room as the rest of the dialog. */
.modal-field {
  display: block;
  margin-top: var(--space-md);
}
/* A field the chosen service does not need. The browser's own rule for [hidden] is display:
   none, and the block above outranks it -- so a field hidden in script stayed on screen,
   asking Backblaze users for an address the region already determines. */
.modal-field[hidden] { display: none; }
.modal-field span {
  display: block;
  color: var(--text-muted);
  font-size: 0.85rem;
  margin-bottom: 0.4rem;
}
.modal-field input {
  width: 100%;
  box-sizing: border-box;
}
/* The field is wrapped in its own label, which is set in semibold, and an input inherits its
   weight -- so the example inside it and anything typed there came out bold, while every
   other field in the product is set at the ordinary weight. The caption stays bold; what is
   in the box does not. */
.modal-field input,
.modal-field select {
  font-weight: 400;
}

/* A switch and whatever belongs to it: the row itself, and the setup that only matters
   while that row is on. The group holds the rule between rows, so a panel opening does not
   push a line out from under its own switch. */
.switch-group { border-bottom: 1px solid var(--border); }
.switch-group:last-child { border-bottom: none; }
.switch-group .switch-line { border-bottom: none; }

/* Opened and closed by a grid row rather than by height, which is the one way to animate
   from nothing to whatever the content turns out to be: a height can only be interpolated
   between two numbers, and the number here is not known until the panel is open. */
.switch-extra {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition:
    grid-template-rows 280ms cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity 200ms ease;
}
.switch-extra > .switch-extra-inner {
  min-height: 0;
  overflow: hidden;
}
.switch-extra.is-open {
  grid-template-rows: 1fr;
  opacity: 1;
}
/* Set in from the name above it, so it reads as belonging to that row rather than as the
   next thing in the table, and given room to breathe once open. No gutter on the right:
   the switch has a column of its own now, so there is nothing on this side to stay clear
   of. */
.switch-extra.is-open > .switch-extra-inner {
  padding: var(--space-xs) 0 var(--space-md) var(--space-md);
}
/* The other kind of panel: not an aside about the service its row names, but the settings
   that row governs. These are the card's own content, so they are full width and set in from
   nothing, and the last of them gives its trailing margin back so the space under the final
   field matches the space over the switch. */
.switch-extra.is-plain.is-open > .switch-extra-inner {
  padding: var(--space-md) 0 0;
}
.switch-extra.is-plain .field:last-of-type { margin-bottom: 0; }
/* Somebody who has asked their system not to animate gets the same panel, immediately. */
@media (prefers-reduced-motion: reduce) {
  .switch-extra { transition: none; }
}

/* Two fields that are halves of one thing -- a key and its secret -- side by side, so they
   read as the pair they are and are copied across in one go. Equal columns, and they stack
   on a narrow screen where two of them would be too cramped to type into. */
.field-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-md);
  align-items: start;
}
.field-pair > .field { margin-top: 0; }

@media (max-width: 40rem) {
  .field-pair { grid-template-columns: 1fr; }
}

/* Six settings that describe one thing between them -- how the courier behaves while it is
   carrying a copy -- rather than six questions asked in turn. Three across and two down puts
   the whole of that description in view at once; down a single column, a reader had to
   scroll to see what the last two said. The grid's own gap does the spacing, so each field
   gives up the margin it carries when it stands alone. */
.field-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
  align-items: start;
}
.field-grid > .field { margin: 0; }

/* Two, then one, as the card narrows. A third of a narrow screen is not a box anybody can
   read a number in, and the point of the grid is being able to read them all. */
@media (max-width: 64rem) {
  .field-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 40rem) {
  .field-grid { grid-template-columns: 1fr; }
}

/* An address that has to be copied exactly, in the middle of a sentence. */
.literal {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
  color: var(--text);
  overflow-wrap: anywhere;
}

/* Something is being done, and the dialog stays open while it is: what is happening, and
   a bar under it. The bar is the one an upload already uses -- one bar in one place -- and
   it is set moving rather than filled, because the server unpacking a file out of an
   archive says nothing at all until it is finished. A bar that crept to ninety percent and
   waited there would be inventing progress it cannot see. */
.modal-working {
  margin: var(--space-md) 0 0;
}
.modal-working .upload-bar {
  margin-top: var(--space-xs);
}

/* The mark on a field that has to be filled in. Colored rather than bold, so it reads as a
   sign beside the label instead of part of the word.

   Written against the field as well as the mark: every span inside a modal field is a
   block, being a caption above its box, and a bare class does not outrank that -- the mark
   sat on a line of its own under the label until this selector was as specific as the rule
   it had to beat. */
.modal-field span.required-mark,
.required-mark {
  display: inline;
  margin: 0;
  color: var(--accent);
}
/* And the same trap as the fields above: an author's display outranks the browser's rule
   for [hidden], so the mark on the one optional field -- a port, which states its own
   default -- stayed on screen after being hidden in script. */
.modal-field span.required-mark[hidden],
.required-mark[hidden] {
  display: none;
}

/* The note under a field: where to find the thing being asked for. Quieter than the label
   above the box and closer to it than to the field below. */
.field-hint {
  display: block;
  margin-top: 0.35rem;
  color: var(--text-muted);
  font-size: 0.82rem;
}

/* The credential box, which takes a password on one line or a private key on several. It
   grows only when a service is chosen that accepts a key, so a password field is never a
   great empty rectangle. */
.modal-field textarea.secret-field {
  width: 100%;
  box-sizing: border-box;
  font-weight: 400;
  resize: vertical;
  font-family: inherit;
}

/* The paths of a partial restore, typed one per line. A monospace box, because what goes
   in it is a path and the difference between two paths is often one character. */
.modal-field.restore-paths textarea {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85rem;
}

/* What a choice costs, where it costs something. Set apart from the quiet hint above it,
   because "the password crosses the network in the clear" is not a hint. */
.modal-caution {
  margin: var(--space-sm) 0 0;
  padding: var(--space-sm) var(--space-md);
  border-left: 3px solid var(--warn);
  background: rgba(209, 154, 60, 0.1);
  color: var(--text);
  font-size: 0.9rem;
}

/* A form rather than a question. Connecting a storage service asks for seven things, and at
   30rem the labels and their fields crowd each other down a narrow column. Its height is
   the same as every other dialog's: the body scrolls, the title and the buttons stay. */
.modal-wide {
  max-width: 38rem;
}
.modal-field select {
  width: 100%;
  box-sizing: border-box;
}
/* The sentence under a section's title, explaining what the section is for.

   Justified and the full width of the box below it, so the paragraph and the thing it
   introduces share an edge on both sides rather than the text stopping short of it. Its top
   margin is given up entirely: the heading's own bottom margin is the whole of the gap, and
   two margins between a title and its first line was more air than a title needs.

   Hyphenation stays manual. Justified text tempts a browser into breaking words across
   lines, and no word in this product is ever split. */
.section-note {
  margin-top: 0;
  text-align: justify;
  hyphens: manual;
}
section:has(> .section-note) .section-head { margin-bottom: var(--space-xs); }

/* A switch and what it governs, on one line: the words at the left where reading starts,
   the switch at the right where the eye finishes, and a rule between rows so a list of them
   reads as a list rather than as one block of text with switches in it. */
.switch-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--border);
}
/* The first and last rows give their outer padding back to the card, so the space above the
   first line and below the last is the card's own and the two match. Left alone, the last
   row kept its fourteen pixels and sat thirty-nine from the bottom against twenty-five at
   the top -- a list that looks like it is missing a row. */
.card > .switch-line:last-child {
  border-bottom: none;
  padding-bottom: 0;
}
/* A reading rather than a switch on the right of the row: the value and the threshold under
   it. Ranged right, so both lines end on the same edge as the pill in the row below them --
   left alone the shorter of the two floated, and a column of readings looked ragged down its
   own margin. A row ending in a toggle is untouched: a button is not a span. */
.switch-line > span:last-child { text-align: right; }
.card > .switch-line:first-child { padding-top: 0; }
/* A row wrapped in a group with its own panel is no longer its card's first or last child,
   so the trimming above has to be said again for the group -- said only by :first-child it
   applied to every row at once, since each is the only line inside its own group, and the
   whole list closed up. */
.switch-group:first-child > .switch-line { padding-top: 0; }
.switch-group:last-child > .switch-line { padding-bottom: 0; }

/* A column holding nothing but a checkbox: as narrow as the box, and the box drawn at the
   size of a box rather than stretched across the column the way form inputs are here. */
.cell-pick,
th.cell-pick {
  width: 2.5rem;
  text-align: center;
  padding-right: 0;
}
.cell-pick input[type="checkbox"] {
  width: auto;
  margin: 0;
  vertical-align: middle;
}

/* A checkbox and its words on one line, with the box aligned to the text rather than
   floating above it: these read as a sentence you agree with, not as a field with a label. */
.check-line {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}
/* Form inputs on these pages are full width, which for a checkbox means a tick floating in
   the middle of a six-hundred-pixel box with its words pushed off to the right. A box is the
   size of a box. */
.check-line input[type="checkbox"] {
  width: auto;
  flex: none;
  margin: 0;
}
.check-line span { display: inline; margin: 0; }

/* The sentence explaining what the empty field and a zero mean. Sits under the input,
   set back from it so it reads as guidance rather than as a second label. */
.modal-hint {
  margin-top: var(--space-sm);
  color: var(--text-muted);
  font-size: 0.85rem;
  line-height: 1.5;
}

/* Two buttons in one cell need space between them, or they read as one control.
   The gap comes from an inner flex box rather than from the cell itself: making a td a
   flex container takes it out of the table layout, so against a real fleet the whole
   actions column detached from its row and sat on the page background instead of the
   row's, with the row's own stripe stopping short of it. */
td.row-actions {
  /* The lifecycle controls (Upgrade, Pause/Resume, Deactivate, Disconnect) can be more
     than fits on one line at a narrow width; let them wrap in place rather than push the
     table wider than the screen. */
  white-space: normal;
}
td.row-actions > .actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-sm);
  align-items: center;
  justify-content: flex-end;
}

/* The selection column in the available list. Sized to the control so the names next
   to it start on a consistent left edge. */
th.pick, td.pick {
  width: 1px;
  padding-right: var(--space-sm);
}
.checkbox {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}
.checkbox input {
  width: 1rem;
  height: 1rem;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

/* The count and the action under a table. They sit apart at the ends on a wide screen
   and stack, in reading order, when there is no room for both on one line. */
.table-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  margin-top: var(--space-md);
}

/* Two actions at the same end of a table's footer.
   The count keeps the left, and these keep the right in the order they are written, so
   the one that acts on what is selected comes before the one that adds something new.
   They wrap together rather than one at a time, which would leave a lone button
   stranded on a line of its own. */
.footer-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
}

/* The section for the one action on a page that cannot be undone.
   Marked by its border rather than by a wash of color: the card still has to be read, and
   the button inside it is already the loudest thing on the page. The space above it is
   wider than between ordinary sections, so it does not read as the next thing to do. */
.danger-zone {
  margin-top: var(--space-xl);
}
.danger-zone .card {
  border-color: var(--danger-strong);
}
.danger-zone .card p {
  margin-top: 0;
  /* Justified, so the paragraph reaches both edges of the card rather than ending each
     line wherever the words happen to run out. What is written here is the last thing
     read before something is deleted, and it should look like a block set down
     deliberately. Words are never broken across lines to achieve it. */
  text-align: justify;
  hyphens: manual;
}

/* The title of a page inside a section, under the row of sub-pages.
   A section's own page carries its heading on the first card; a page inside one has no
   card to carry it, so the heading stands alone and needs the space a section would have
   put above it. */
.section-head-page {
  margin-top: var(--space-lg);
}

/* A quiet line under a form's action -- "forgot your password?", "back to sign in".
   Set apart from the button so it reads as a way out rather than a second action. */
.form-aside {
  margin-top: var(--space-md);
  margin-bottom: 0;
  font-size: 0.9rem;
  color: var(--text-muted);
}

/* Three across for city, region, and postal code, collapsing to one when there is no
   room. Matches .grid-2, which the connection form already uses. */
.grid-3 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: var(--space-md);
  /* Fields align at the top, so a hint under one does not shift its neighbors. */
  align-items: start;
}

/* The picture beside its controls, wrapping to stacked on a narrow screen. */
.avatar-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-lg);
  flex-wrap: wrap;
}
.avatar-controls { flex: 1 1 20rem; }

/* Upload and Remove sit on one line with room between them. Remove was styled as a
   link, which gave a destructive action the visual weight of a footnote beside the
   button it undoes; as a button the two read as the pair of choices they are. */
.avatar-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
}

.avatar {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--border);
  background: var(--surface-raised);
  flex: 0 0 auto;
  display: block;
}

/* Before a picture is uploaded: the initial, at the same size as the image will be, so
   the layout does not shift when one arrives. */
.avatar-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.4rem;
  font-weight: 600;
  color: var(--text-muted);
}

/* Two columns, and only ever two.
   The identity block has four fields. Left to auto-fit they land three across with the
   fourth alone on a row of its own, which reads as a mistake rather than a layout. */
.grid-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-md);
  align-items: start;
}
@media (max-width: 640px) {
  .grid-pair { grid-template-columns: 1fr; }
}

/* The mark on a required field.

   Taken from the control rather than from a class on the label, so it cannot disagree
   with what is actually enforced: a field that stops being required loses its asterisk
   in the same edit, and one that becomes required gains it without anybody remembering
   to. A label that promises optional over a control the server refuses is worse than no
   mark at all.

   Scoped to the two detail forms. The avatar picker's file input is required to submit
   that form, but choosing a picture is not required of anybody, and an asterisk there
   would say it was.

   The asterisk is decoration: `required` is already on the control, which is what a
   screen reader announces, so repeating it as text would say it twice. */
#profile-form .field:has(:required) > label::after,
#register-form .field:has(:required) > label::after {
  content: '*';
  color: var(--accent);
  margin-left: 0.25rem;
  font-weight: 600;
}

/* The trail back up, under the title.

   Separators are drawn by the stylesheet rather than typed into the markup, so the
   character can change in one place and a screen reader is never read a slash between
   every step. The last step is where the reader is and is not a link. */
.crumbs {
  margin: calc(var(--space-xs) * -1) 0 var(--space-md);
  font-size: 0.85rem;
  color: var(--text-muted);
}
.crumbs ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}
.crumbs li + li::before {
  content: '/';
  margin-right: 0.4rem;
  color: var(--border);
}
.crumbs a { color: var(--text-muted); }
.crumbs a:hover { color: var(--text); }
.crumbs [aria-current='page'] { color: var(--text); }

/* The password strength meter.
   Sits under its field, close enough to read as part of it and set apart enough not to
   crowd the input. */
.strength {
  margin-top: var(--space-sm);
  /* The meter is the container the requirement columns are measured against; see the
     container query below. */
  container-type: inline-size;
}

.strength-bar {
  height: 6px;
  border-radius: 3px;
  background: var(--surface-raised);
  overflow: hidden;
}
.strength-bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: var(--danger);
  transition: width 140ms ease, background-color 140ms ease;
}
/* One color per level, walking red to green.
   Three shades covering five states meant two pairs of levels looked identical, so
   satisfying a rule appeared to do nothing. The steps run through red, orange, amber
   and yellow-green to green, each a visible move from the one before. */
.strength[data-level="1"] .strength-bar span { background: var(--strength-1); }
.strength[data-level="2"] .strength-bar span { background: var(--strength-2); }
.strength[data-level="3"] .strength-bar span { background: var(--strength-3); }
.strength[data-level="4"] .strength-bar span { background: var(--strength-4); }
.strength[data-level="5"] .strength-bar span { background: var(--strength-5); }

/* The wording follows the same scale, so the label is not the only thing carrying the
   state for anyone who cannot separate the colors. */
.strength[data-level="1"] .strength-label { color: var(--strength-1); }
.strength[data-level="2"] .strength-label { color: var(--strength-2); }
.strength[data-level="3"] .strength-label { color: var(--strength-3); }
.strength[data-level="4"] .strength-label { color: var(--strength-4); }
.strength[data-level="5"] .strength-label { color: var(--strength-5); }

.strength-label {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* The requirements, as a checklist rather than a paragraph nobody reads. */
.strength-list {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: grid;
  /* Stacked by default, which is what a narrow panel gets. The five-column form is
     opted into below, where there is room for it. */
  grid-template-columns: 1fr;
  gap: 0.3rem 0.9rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* Three columns, filled downward: lowercase and uppercase, then number and special,
   then length on its own. Two rows are declared and the flow is by column, so the items
   fall down each column in turn rather than across each row -- which is what makes the
   third column hold one item instead of leaving a hole in the middle of the second row.

   Asked of the viewport this would be wrong: the same meter sits in a 26rem panel on
   the reset page and in a 44rem one on the register form, and at any given window
   width those two need different answers. The question is how much room the meter
   itself has, so the meter is the container that gets asked.

   38rem is where three columns hold the longest requirement without breaking it. Below
   that the list stacks, which is still in order. */
@container (min-width: 38rem) {
  .strength-list {
    grid-template-columns: none;
    grid-template-rows: repeat(2, auto);
    grid-auto-flow: column;
    grid-auto-columns: minmax(0, 1fr);
  }
}

.strength-list li {
  display: flex;
  /* Top, not center: the longest requirement wraps to a second line in a narrow
     column, and centered it would sit the tick between the two lines. */
  align-items: flex-start;
  gap: 0.5rem;
}
/* An empty ring until met, a filled check afterwards. Shape as well as color, so the
   state does not depend on distinguishing red from green. */
.strength-list .tick {
  width: 0.85rem;
  height: 0.85rem;
  /* Centered on the first line of the label rather than on the block. */
  margin-top: 0.2rem;
  border-radius: 50%;
  border: 1px solid var(--border);
  flex: 0 0 auto;
}
.strength-list li.met { color: var(--ok); }
.strength-list li.met .tick {
  border-color: var(--ok);
  background: var(--ok);
  box-shadow: inset 0 0 0 2px var(--surface);
}

/* The account form asks for an address, so it needs more room than a sign-in panel
   without becoming a full-width sheet of inputs. */
.auth-panel-wide { max-width: 44rem; }

/* A single reading: what it is, what it means, and how it has moved. */
.reading-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: var(--space-md);
}

.reading {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-left-color: var(--border);
  border-radius: var(--radius);
  padding: var(--space-md);
}
/* Severity is carried by a bar down the left edge as well as by the words, so the state
   does not depend on separating colors. */
.reading-notice   { border-left-color: var(--accent); }
.reading-warning  { border-left-color: var(--warn); }
.reading-critical { border-left-color: var(--danger); }

.reading-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
}
.reading-label { font-weight: 600; }
.reading-value {
  font-size: 1.35rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}

/* The plain sentence. This is the layer most people read and never leave. */
.reading-meaning {
  margin: 0.5rem 0 0;
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.5;
}

.reading-alert {
  margin: 0.6rem 0 0;
  font-size: 0.9rem;
  color: var(--warn);
}
.reading-critical .reading-alert { color: var(--danger); }

.spark {
  display: block;
  width: 100%;
  height: 40px;
  margin-top: var(--space-md);
  color: var(--text-muted);
}
.spark-empty { margin: var(--space-md) 0 0; font-size: 0.85rem; }

/* The raw layer. Closed by default, and never more than one click away. */
.reading-raw {
  margin-top: var(--space-md);
  border-top: 1px solid var(--border);
  padding-top: var(--space-sm);
}
.reading-raw summary {
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--text-muted);
}
.reading-raw dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.3rem var(--space-md);
  margin: var(--space-sm) 0 0;
  font-size: 0.85rem;
}
.reading-raw dt { color: var(--text-muted); }
.reading-raw dd { margin: 0; word-break: break-word; }

.notice-warn {
  background: rgba(209, 154, 60, 0.14);
  border: 1px solid rgba(209, 154, 60, 0.35);
  color: #e8c88a;
}

/* Work in progress, as distinct from a state that has settled. Amber rather than green
   or red: nothing has succeeded or failed yet, and saying either would be a guess.
   The pulse is slow and low-contrast on purpose -- it signals activity without
   competing with the content next to it. */
.pill-working {
  background: rgba(209, 154, 60, 0.16);
  color: var(--warn);
  animation: working-pulse 2.4s ease-in-out infinite;
}
.notice-working {
  background: rgba(209, 154, 60, 0.10);
  border-color: rgba(209, 154, 60, 0.32);
  color: #e8c88a;
}

@keyframes working-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* Anyone who has asked not to see movement gets the same information without it. */
@media (prefers-reduced-motion: reduce) {
  .pill-working { animation: none; }
}

/* Cells do not wrap, which is what keeps columns sized to their content -- but a
   sentence in a cell is not content of that kind. Left as nowrap, the searching hint
   widened the Label column by four hundred pixels and pushed the row's own button
   outside the table. An upstream error message in the same cell would do it worse. */
td .hint {
  white-space: normal;
  max-width: 26rem;
}

/* A filter above a long table. Full width so it reads as belonging to the table
   underneath rather than floating beside it, and with room below so the two are
   separate blocks rather than one crowded one. */
.table-filter {
  margin-bottom: var(--space-md);
}
.table-filter input {
  width: 100%;
  max-width: 28rem;
}

/* Present for screen readers, absent on screen: the filter's placeholder already says
   what it does, so a visible label would repeat it. */
.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;
}

/* The connector disclosure on the connection form. Set apart from the fields by its own
   space and its quieter color rather than by a rule down its side: it is four paragraphs
   now, one for each tier, and a bar beside a block that tall reads as a quotation. */
.disclosure {
  margin: var(--space-lg) 0;
  max-width: 46rem;
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.6;
}

.disclosure p { margin: 0 0 var(--space-sm); }
.disclosure p:last-child { margin-bottom: 0; }

/* Three columns, one per tier, so the three are read as alternatives rather than as a
   sequence. Its own max-width is dropped: that figure was set for one column of prose
   and here it would squeeze three into a third of it each. */
.disclosure.grid-3 { max-width: none; }

/* One tier's note, under a heading that spans it: the title names what is being
   connected, and what that does is read across two columns beneath it. The max-width
   goes for the same reason it goes above -- it measures one column, not two. */
.disclosure-split { max-width: none; }

.disclosure-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-lg);
}

.disclosure-columns p:last-child { margin-bottom: 0; }

@media (max-width: 640px) {
  .disclosure-columns { grid-template-columns: 1fr; }
}

/* The row of fields sitting directly above the note.
   The row carries its own trailing margin and the note carries a matching one above it,
   and the two together read as twice the space anything else on the page is given. */
.grid-pair:has(+ .disclosure) .field,
.grid-3:has(+ .disclosure) .field { margin-bottom: 0; }

.disclosure h3 {
  margin: 0 0 var(--space-xs);
  font-size: 0.9rem;
  color: var(--text);
}

/* The info icon on a settings row, and the recommendation block in its modal. */
.icon-button {
  /* Laid out as a box of its own, so a cell can place it.

     A <button> is one already; a link is not. An <a class="icon-button"> whose only child is
     a drawn icon -- a block-level <svg> -- becomes a block box the width of the cell, and a
     block box is not something text-align can move: the column said "right" and the icon sat
     at the left of a box that filled the column. As a shrink-to-fit box it goes where the
     cell puts it. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  color: var(--accent);
  border: none;
  padding: 0;
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
}
/* An icon is a mark, not a control with edges. The button rule above draws a hairline
   inside every button on hover, which reads as a change of state on something that has a
   fill and a label and as a box drawn around something that has neither. */
.icon-button:hover {
  background: transparent;
  color: var(--accent-hover);
  box-shadow: none;
}
/* An icon drawn as a shape rather than typed as a character sits on the text baseline by
   default, which leaves it hanging below the words beside it. */
.icon-button svg { display: block; }

/* How old the page is, and the way to make it newer. The control is part of the sentence
   rather than a button parked beside it, so it sits on the line and takes its space from the
   words, not from a row of its own. */
.updated-line {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}
.recheck-now[disabled] { opacity: 0.5; cursor: progress; }
.setting-title { font-weight: 600; }
/* Weight is the label's own; everything else comes from the shared heading rule above, so a
   change to the title style carries to the section labels rather than drifting from them. */
.modal .modal-reco-label { font-weight: 600; }

/* The location breadcrumb in a setting's info modal. Segments that link into cPanel read as
   links; the separators are muted so the path scans as one line. */
.modal-crumbs {
  /* Same rhythm as any other paragraph in the modal: its label provides the space above, and
     it keeps the same space below, so the sections are evenly spaced down the dialog. */
  margin: 0 0 var(--space-lg);
  line-height: 1.7;
}
.modal-crumbs a { color: var(--accent); text-decoration: none; }
.modal-crumbs a:hover { text-decoration: underline; }
.modal-crumbs .crumb-sep { color: var(--text-muted); }

/* Per-domain switches under a setting that is held per domain. The list is indented beneath
   the setting it belongs to, so it reads as part of that row rather than a second column of
   controls, and each line is a small switch followed by the domain it governs. */
.domain-switches {
  margin: var(--space-sm) 0 0;
  padding-left: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.domain-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}
.domain-name {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* The switch: a track that turns green when on, with a knob that slides. It is a button, so
   the confirmation still runs before anything is written -- the knob moves only after the
   page reloads with the state the server actually stored. */
.toggle {
  flex: none;
  width: var(--toggle-width);
  height: 18px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: var(--border);
  position: relative;
  cursor: pointer;
  transition: background 120ms ease;
}
.toggle:hover { background: var(--text-muted); }
.toggle[aria-checked="true"] { background: var(--ok); }
.toggle[aria-checked="true"]:hover { filter: brightness(1.08); }
.toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.toggle .toggle-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #fff;
  transition: transform 120ms ease;
}
.toggle[aria-checked="true"] .toggle-knob { transform: translateX(16px); }
.toggle[disabled] { opacity: 0.6; cursor: default; }

/* The control and the info icon get a column each, sized to their contents, so a toggle lines
   up under a toggle and an icon under an icon however wide the setting text beside them runs.
   A row with no control keeps its empty cell, which is what holds the alignment. */
/* Every settings table lays out on the same fixed columns, so the status, the control, and
   the info icon sit at the same place in each section of a page. Sized to content, each table
   computed its own widths from whatever text it happened to hold, and the columns stepped in
   and out as the reader moved down the page. The control column is declared even where a
   section has no toggles, because an absent column is what pulls the ones beside it out of
   line. */
/* The three lists of items -- servers, hosting, websites -- are one table with different
   rows in it. Sizing itself to its contents, each page sized its columns to whichever
   names it happened to hold, and the same six columns landed somewhere different on all
   three. The five that hold the same thing wherever they appear are given their width;
   the name, the only column whose content varies, takes whatever is left, which also
   makes it the widest. The floor is there so a narrow window scrolls the table rather
   than crushing the names to nothing. */
.items-table {
  table-layout: fixed;
  width: 100%;
  min-width: 46rem;
}

.items-table th.pick { width: 3.5rem; }
.items-table th.col-access { width: 10.5rem; }
.items-table th.amount { width: 9rem; }
.items-table th.col-billed { width: 9rem; }
/* The two readings a hosting list carries after the five every list carries. Narrower
   than the rest: a percentage and a word are the shortest things in the table. */
.items-table th.col-reading { width: 7rem; }

/* A name too long for the column it was given wraps inside it. Everything else in the
   row is a pill, a figure or a control, and none of those has anywhere to wrap to. */
.items-table td:nth-child(2) {
  white-space: normal;
  overflow-wrap: anywhere;
}

.settings-table {
  table-layout: fixed;
  width: 100%;
  min-width: 0;
}
.settings-table .cell-status { width: 8rem; }
/* The status pill is the tallest thing in a row and the one a reader looks at, so the switch
   and the info icon are aligned to it. Neither carries text the way the pill does, so on the
   shared text baseline both sat above it; each is nudged down by the measured difference
   between its center and the pill's. */
.settings-table .cell-control .toggle { margin-top: 5px; }
.settings-table .cell-info .icon-button { margin-top: 5px; }
.settings-table .cell-control { width: 4rem; }
.settings-table .cell-info { width: 4rem; }
.cell-control {
  white-space: nowrap;
  text-align: center;
}
/* The last column ends where the row's text begins, mirrored.

   Centering it left the info icon floating in the middle of its own column, a hair from the
   table's edge, while the row's first word sat a clear --space-md in from the other side. The
   icon is aligned to the end of the cell instead, so the cell's own padding is the whole gap
   and the two margins match. */
.cell-info {
  white-space: nowrap;
  text-align: right;
}
/* A table whose rows end in an act rather than an icon. The button is wider than an icon, so
   the column is, and it keeps the same margin to the edge. */
/* A column of icons is read down its middle: the mark is the same width in every row, so
   centering lines them up under their heading rather than scattering them. */
.settings-table.has-action .cell-action { width: 9.5rem; text-align: center; white-space: nowrap; }
.settings-table.has-action th.cell-action { text-align: center; }
/* The setting's own cell takes the space left over, and its text wraps inside it rather than
   forcing the table wider than the page. */
.settings-table td:first-child,
.settings-table th:first-child {
  white-space: normal;
}

/* The tools at the end of a backup row: three marks with air between them, so the one that
   deletes cannot be hit while reaching for the one that downloads. */
.row-tools {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  /* Level with the first line of text beside it. An inline box with no baseline of its own
     hangs from the line's baseline, which leaves a row of marks sitting a few pixels above
     the words they belong to -- read as "too high" long before anybody measures it. Middle
     puts their center on the baseline plus half an x-height, which is where the eye finds
     the middle of a line. */
  vertical-align: middle;
}
/* A tool this row cannot use. Shown rather than hidden, because its absence would read as a
   missing feature and its tooltip is the explanation. */
.icon-button.is-off {
  color: var(--text-muted);
  opacity: 0.45;
  cursor: default;
}

/* The Type and Size columns of the backups table: narrow, sized to what they hold, and read
   down their middles -- a column of pills and a column of short figures both look ragged
   against an edge. */
.settings-table .cell-kind,
.settings-table th.cell-kind,
.settings-table .cell-size,
.settings-table th.cell-size {
  width: 7rem;
  text-align: center;
}

/* The tools column takes only what the icons need, so the date column beside it stretches
   and the icons end up hard against the right edge of the table.

   A measured width rather than a shrink-to-fit one: this table lays out fixed, where a
   percentage is obeyed literally -- `width: 1%` gave a column eight pixels wide with the
   icons clipped out of sight. Three marks at 15px, two gaps at --space-sm, and the cell's
   own padding either side come to a little over 7.5rem. */
.settings-table.action-end .cell-action,
.settings-table.action-end th.cell-action {
  width: 7.6rem;
  white-space: nowrap;
  text-align: left;
}

/* The schedules table. Its last column holds a switch rather than a mark, and the column
   before it a full date and time -- both wider than the columns a settings row is built
   for. This table lays out fixed, so a column too narrow for what is in it does not grow:
   the button rode over the date beside it and the remove mark fell off the right edge. */
/* Two marks rather than three, so narrower than the backups table beside it -- and the
   room that gives back goes to the column naming the service. */
/* A schedule can be moved to where its client wants it read.

   The grip says so before anything is touched: two columns of dots at the head of the
   row, quiet until the pointer is near it. It owns the touch as well, which is how this
   gesture works on a phone without taking the page's own scrolling away from the table. */
/* The grip and what it holds, with room between them: a handle that touches the words it
   carries reads as part of them. */
.drag-line {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  min-width: 0;
}
.drag-line > div { min-width: 0; }
.drag-grip {
  display: inline-flex;
  align-items: flex-start;
  color: var(--text-muted);
  cursor: grab;
  touch-action: none;
  flex: none;
  /* A flex item takes no part in baseline alignment, so the grip is set against the line
     beside it by hand: this is the measured distance that puts the middle of the dots on
     the middle of the first line of text -- level with the marks at the other end of the
     row, which find the same place through vertical-align. */
  padding-top: 0.3rem;
}
.plan-table tbody tr:hover .drag-grip { color: var(--text); }

.plan-table tbody tr[data-plan-row] {
  cursor: grab;
}

/* Lifted, and moving with the pointer. The shadow is on the cells because a row in a
   collapsed table draws no box of its own to hang one from; together they read as one
   band coming off the page. */
.plan-table tbody tr.is-dragging {
  cursor: grabbing;
  position: relative;
  z-index: 3;
}
.plan-table tbody tr.is-dragging td {
  background: var(--surface-raised);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.55);
  border-bottom-color: transparent;
}
.plan-table tbody tr.is-dragging .drag-grip { cursor: grabbing; color: var(--accent); }

/* Where it would land if it were let go now, shown by the list itself: the rows it has
   passed slide out of its way, opening the gap it would drop into. The transition is set
   on the rows as they move rather than declared here, so the row being carried is the one
   thing on the table that follows the pointer without any easing of its own. */

/* Nothing is selected while a row is being dragged: a drag across a table otherwise
   highlights every word it passes over. */
.plan-table tbody tr.is-dragging,
.plan-table:has(tr.is-dragging) tbody {
  user-select: none;
}

/* Three marks now, read down the middle of their column, the same as the tools on a
   storage service. */
.settings-table.plan-table .cell-action,
.settings-table.plan-table th.cell-action {
  /* The marks, and the same air either side of them that the first column gives its own
     text -- so the row ends as far from the table's edge as it begins from it. Written as
     the sum it is rather than the number it comes to: the marks are centered in this
     column, so whatever is left over is split between them and the edge, and asking for
     the marks plus two gutters is asking for exactly one gutter on the outside.

     It was 6.5rem when there were three marks, and a fourth pushed the table wider than
     the frame around it, which is what running off the side looks like. Then it was 8rem,
     which fit but left the row ending 13px from the edge where it had begun 24px from it.
     Asking the column to size itself to its content instead does not work here: the marks
     sit in a flex box, whose smallest size a browser reckons as nothing at all, so the
     column collapsed to its padding and the marks hung out of it.
     Five marks now, since a schedule can be run by hand. Written as the sum so that the
     next one to arrive is a change to one number rather than a measurement taken again. */
  width: calc(5 * 15px + 4 * var(--space-sm) + 2 * var(--space-md));
  text-align: center;
  padding-left: var(--space-xs);
  padding-right: var(--space-xs);
}

/* Both middle columns are read down their centers, and both are wide enough for what is
   in them to sit there.

   Width is the whole of it: a cell carries 24px of padding either side, so a column of
   6rem leaves 48px for a word like "Retention" to be centered in. It cannot be, so it
   overflowed to the right and every value under it read as pushed off center by a dozen
   pixels while the stylesheet said "center" all along. */
.settings-table.plan-table .cell-kind,
.settings-table.plan-table th.cell-kind,
.settings-table.plan-table .cell-when,
.settings-table.plan-table th.cell-when {
  text-align: center;
  /* The cell's usual 24px either side is room a wide first column wants more: at that
     padding a date needs a 13.5rem column to sit in the middle of, and the folder path
     beside it then wrapped. The air these two need is between the columns, not inside
     them, so it is the padding that gives way. */
  padding-left: var(--space-xs);
  padding-right: var(--space-xs);
}
.settings-table.plan-table .cell-kind,
.settings-table.plan-table th.cell-kind {
  /* The column now says what the retention comes to -- "About 11 months" under the count --
     and that line is the widest thing it holds. */
  width: 8rem;
}
.settings-table.plan-table .cell-when,
.settings-table.plan-table th.cell-when {
  width: 12.5rem;
  white-space: nowrap;
}
/* The dates stay on one line; a refusal in the service's own words does not. Held to one
   line it ran out past both edges of its column and over the retention beside it. */
.settings-table.plan-table .cell-when .wrap-tight { white-space: normal; }

/* Where a service writes, under its name. Set smaller than the name and stepped in from
   it, so the pair reads as a thing and then the place inside it rather than as two lines
   of equal weight. */
.service-path,
.service-guard {
  font-size: 0.78rem;
  padding-left: var(--space-sm);
}

/* Where you are in the account, under the title. Smaller than the words it sits beneath,
   and every part of it a step: the folder three levels up is one press by its own name
   rather than three presses of a climb. */
.tree-trail {
  margin: 0 0 var(--space-md);
  font-size: 0.85rem;
  color: var(--text-muted);
  word-break: break-word;
}
.tree-trail-sep {
  color: var(--text-muted);
  margin: 0 0.35rem;
}
/* The one at the front has nothing to its left, so it keeps only the space on its right
   and starts where the title above it starts. */
.tree-trail-lead { margin-left: 0; }
/* The folder being read is named rather than linked: a link to where you already are is a
   link that does nothing. */
.tree-trail-here { color: var(--text); }

/* The account as the backups hold it. Its tools are read down the middle of their column,
   the same as every other row of marks, and the list runs to its own length rather than
   being capped: this is a thing to look down, not a summary. */
.settings-table.tree-table .cell-action,
.settings-table.tree-table th.cell-action {
  width: 7rem;
  text-align: center;
}

/* Both backup tables read their marks down the middle of the column, the same as every
   other row of tools in the product. */
.settings-table.held-table .cell-action,
.settings-table.held-table th.cell-action,
.settings-table.copies-table .cell-action,
.settings-table.copies-table th.cell-action {
  text-align: center;
}

/* The copies in a client's own storage.

   The date column is the shared 7rem one, and a date written out with its dashes wants
   116px of it -- four pixels more than it has, which the fixed layout then carried past
   the right edge of the table. Widened to the thing in it.

   And a file name is one long unbroken run of characters, so it is allowed to wrap at the
   dashes it already has rather than push its own column wider. */
.copies-table td:first-child {
  overflow-wrap: break-word;
}
.copies-table .cell-size,
.copies-table th.cell-size {
  width: 8rem;
}

/* How a service last answered, as a filled circle at the end of its row. The three colors
   are the ones its pill wore, so a reader who knew the pills knows these.

   Each keeps its color under the pointer: every other mark in a row lightens toward the
   accent on hover, which on a green circle would read as the state having changed. */
.icon-button.dot-ok { color: var(--ok); }
.icon-button.dot-ok:hover { color: var(--ok); }
.icon-button.dot-bad { color: var(--danger); }
.icon-button.dot-bad:hover { color: var(--danger); }
.icon-button.dot-idle { color: var(--text-muted); }
.icon-button.dot-idle:hover { color: var(--text-muted); }

/* The tools on a storage service, read down the middle of their column: four marks of the
   same width in every row line up under their heading rather than scattering from an edge. */
.settings-table.destination-table .cell-action,
.settings-table.destination-table th.cell-action {
  width: 10rem;
  text-align: center;
}

/* Where the server sends copies. Its status column carries longer words than a settings row
   does -- "Switched Off" is a hundred pixels wide against a column of a hundred and
   twenty-eight, which left the pill four pixels from the table's edge while every other
   column on the page keeps a --space-md. The column is widened to the words rather than the
   words shortened to the column, and the pill is aligned to the end of the cell so its
   margin to the edge is the cell's own padding, matching the gap the first column's text
   starts at on the other side. */
.destination-table .cell-status,
.destination-table th.cell-status {
  width: 10rem;
  text-align: right;
}

/* What the server keeps, under the heading and above the list it explains. Full width, so
   it ends where the table beneath it ends: a paragraph stopping short of the thing it
   introduces reads as a different column of the page. */
.retention-line { margin: 0 0 var(--space-md); }

/* An archive on its way up. A bar rather than a turning mark, because this is the one wait
   in the application whose length is known: the browser says how much of the file has gone.
   Beside it, in words, in case the bar alone is not clear. */
/* A class that sets display beats the browser's own rule for [hidden], so the element has to
   be told to stay out of the way while it is not wanted. */
.upload-progress[hidden] { display: none; }
.upload-progress {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-top: var(--space-md);
  max-width: 32rem;
}
.upload-bar {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: var(--surface-raised);
  overflow: hidden;
}
.upload-bar-fill {
  width: 0;
  height: 100%;
  background: var(--accent);
  transition: width 200ms ease-out;
}
/* The same bar, inside the row of the thing it is measuring. A restore from off-site is
   watched where the copy is listed rather than in a modal: it runs for hours, and nothing
   that runs for hours belongs in a box over the page. */
.row-progress {
  margin-top: var(--space-xs);
  gap: var(--space-xs);
  max-width: 22rem;
}

/* A wait with no measure to it: a short band that travels the bar. Somebody who has asked
   for less movement gets a filled bar that does not travel, and the sentence above it is
   what was carrying the meaning anyway. */
.upload-bar-fill.is-waiting {
  width: 35%;
  transition: none;
  animation: waiting 1400ms ease-in-out infinite;
}
@keyframes waiting {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(300%); }
}
@media (prefers-reduced-motion: reduce) {
  .upload-bar-fill.is-waiting { width: 100%; animation: none; opacity: 0.5; }
}

/* A copy being made, or an account being put back: a row that is not a record of something
   that happened but a report of something happening. The mark turns while it does. */
.row-working .setting-title { color: var(--text-muted); }
.spinner {
  display: inline-block;
  width: 13px;
  height: 13px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 900ms linear infinite;
  vertical-align: -2px;
  margin-right: var(--space-xs);
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; border-top-color: var(--border); }
}

/* The addresses an alert about one hosting account would go to.

   Its own widths rather than the settings table's: those are sized for a pill, a switch and
   an info icon, and a "Remove" button put in a four-rem column runs straight off the edge of
   the card. The address itself takes what is left and wraps, because an address is longer
   than the space anyone would design for it. */
.recipients-table { table-layout: auto; width: 100%; }
.recipients-table td:first-child,
.recipients-table th:first-child {
  white-space: normal;
  overflow-wrap: anywhere;
}
.recipients-table .cell-source { width: 10rem; text-align: right; white-space: nowrap; }
.recipients-table .cell-action { width: 9rem; text-align: right; white-space: nowrap; }

/* The list of settings a reader should look at, inside the summary at the top of the page. */
.attention-list {
  margin: var(--space-sm) 0 0;
  padding-left: var(--space-lg);
}
.attention-list li { margin: 0 0 var(--space-xs); }

/* The sub-pages of a hosting account. A quiet row that reads as navigation rather than as
   controls: the current page is marked by weight and a rule beneath it, not by a filled
   button competing with the actions on the page. */
.account-tabs {
  display: flex;
  flex-wrap: wrap;
  /* Across and down are different distances. Eleven pages wrap onto three lines on a
     phone, and one gap for both put twenty-four pixels between rows on top of the
     fourteen each tab already carries above and below its own word -- three lines with
     more air between them than the words had height. */
  column-gap: var(--space-md);
  row-gap: 0;
  margin: var(--space-md) 0 var(--space-lg);
  border-bottom: 1px solid var(--border);
}
/* The pages inside one tab, in a quieter row under it. Smaller and closer together than
   the tabs above, so the two rows read as a hierarchy -- which part of the account, and
   then which part of that -- rather than as two rows of equal choices. */
.account-subtabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin: calc(var(--space-lg) * -1 + var(--space-sm)) 0 var(--space-lg);
}
.account-subtab {
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.9rem;
  white-space: nowrap;
}
.account-subtab:hover { color: var(--text); text-decoration: none; }
.account-subtab.is-current { color: var(--accent); }

.account-tab {
  padding: var(--space-sm) 0;
  margin-bottom: -1px;
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}
.account-tab:hover { color: var(--text); text-decoration: none; }
.account-tab.is-current {
  color: var(--text);
  font-weight: 600;
  border-bottom-color: var(--accent);
}

/* The Statistics page: a row of period choices, a row of metric toggles, then a card per
   metric. The toggles read as choices rather than as buttons, so they do not compete with the
   actions elsewhere on the account.

   This was once also called .stat-row, which is the class the tiles at the top of the
   dashboard use. Sharing the name meant this flex layout, defined later in the file, quietly
   won -- and every row of tiles in the product stopped being an even grid and started sizing
   each tile to its own contents. A label beside some toggles and a row of equal tiles are
   different things and no longer answer to the same name. */
.stat-controls { margin: var(--space-lg) 0; }
.stat-choice {
  display: flex;
  align-items: baseline;
  gap: var(--space-md);
  margin-bottom: var(--space-sm);
  flex-wrap: wrap;
}
.stat-label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  min-width: 5rem;
}
.stat-periods { margin: 0; border-bottom: none; }
.stat-metrics { display: flex; flex-wrap: wrap; gap: var(--space-sm); }
.stat-toggle {
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.85rem;
  white-space: nowrap;
}
.stat-toggle:hover { color: var(--text); text-decoration: none; }
.stat-toggle.is-on {
  color: var(--text);
  border-color: var(--accent);
  background: rgba(214, 106, 63, 0.12);
}
.stat-card { margin-bottom: var(--space-lg); }
.stat-now { font-size: 1.4rem; font-weight: 600; }
.stat-chart { margin: var(--space-md) 0 var(--space-sm); overflow-x: auto; }
/* The chart on the Statistics page: full width of its card, and tall enough that a month of
   readings is legible rather than a squiggle.

   It scales as a whole rather than stretching to its box. The old drawing did stretch, which
   is harmless for a bare line and ruinous once there is lettering on it -- the labels would
   be squashed narrow on a wide card and drawn out on a phone. */
.stat-graph {
  display: block;
  width: 100%;
  height: auto;
  color: var(--accent);
}
.stat-line { stroke: var(--accent); }
/* The scale sits behind the reading, not beside it: lines quiet enough to measure against
   and not so bright that the eye reads them as data. */
.stat-grid { stroke: var(--border); stroke-width: 1; }
.stat-axis { stroke: var(--border); stroke-width: 1.5; }
.stat-axis-text {
  fill: var(--text-muted);
  font-size: 13px;
  font-family: inherit;
}
.stat-axis-title {
  fill: var(--text-muted);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  letter-spacing: 0.02em;
}

/* The pointer's two marks: a line down to the date and a dot on the reading itself. Both are
   drawn from the start and kept invisible, so following the mouse moves an element that is
   already there rather than building one on every mouse event. */
.stat-guide,
.stat-dot {
  opacity: 0;
  transition: opacity 90ms ease-out;
}
.stat-guide { stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 3 3; }
.stat-dot { fill: var(--accent); stroke: var(--bg); stroke-width: 2; }
.stat-graph.is-tracking .stat-guide,
.stat-graph.is-tracking .stat-dot { opacity: 1; }

/* What that point was, beside the pointer. Fixed rather than absolute so it needs no page
   coordinates, and transparent to the mouse so it cannot get between the pointer and the
   chart it is describing. */
.stat-tooltip {
  position: fixed;
  z-index: 60;
  pointer-events: none;
  max-width: 15rem;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-raised);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
  font-size: 0.85rem;
  line-height: 1.45;
  white-space: nowrap;
}
.stat-tooltip .stat-tooltip-value { font-weight: 600; }
.stat-tooltip .stat-tooltip-when { color: var(--text-muted); }

/* ---------------------------------------------------------------------------
   Lists: sortable headings, a search box, and a chosen number of rows.

   A connected server can produce a hundred and fifty-eight hosting accounts. The
   table stops growing the page at a chosen number of rows and scrolls the rest
   inside itself, which is why the heading row has to stay put -- a sortable
   heading you have scrolled past is no use to anyone.
   --------------------------------------------------------------------------- */

.list {
  margin: 0;
}

/* The search on the left, the row count on the right, both clear of the table
   below them. They read as one bar belonging to the table, not as two stray
   controls floating above it. */
.list-controls {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-md);
}

.list-search {
  flex: 1 1 18rem;
  min-width: 0;
}

.list-search input {
  width: 100%;
  max-width: 28rem;
}

.list-rows {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  flex: 0 0 auto;
}

.list-rows label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
  margin: 0;
  white-space: nowrap;
}

.list-rows select {
  width: auto;
  min-width: 5.5rem;
  margin: 0;
}

/* Under the table, where it reads as a footnote to what was shown rather than a
   claim made before it. */
.list-count {
  margin: var(--space-sm) 0 0;
}

/* Sharing the footer with a control. The row already carries the space above it, and a
   margin of its own here would push the count off the button's center line. */
.table-footer > .list-count {
  margin: 0;
}

/* Sticky needs a scroll container to stick within, which is exactly what the
   constrained height creates. The heading keeps its own background so rows
   cannot show through it as they pass underneath. */
.table-wrap.is-scrolling {
  overflow-y: auto;
}

.table-wrap.is-scrolling thead th {
  position: sticky;
  /* A pixel above the top rather than level with it. Collapsed borders put the cell's
     edge a pixel inside the scrolling container, and rows passing underneath showed
     through the gap as a sliver of text above the headings. */
  top: -1px;
  z-index: 2;
  background: var(--surface-raised);
}

/* The row of column filters, under the headings.
   A quieter band than the headings above it: these are controls you reach for
   occasionally, and at full strength they compete with the data for attention. */
.list-filter-row th {
  padding-top: var(--space-xs);
  padding-bottom: var(--space-xs);
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
}

/* The second sticky row sits below the first rather than on top of it. The offset is
   measured and set by the script; the fallback is only for the moment before it runs. */
.table-wrap.is-scrolling .list-filter-row th {
  top: calc(var(--head-row-h, 2.75rem) - 1px);
}

.list-filter {
  width: 100%;
  min-width: 0;
  padding: 0.3rem 0.45rem;
  font: inherit;
  font-size: 0.85rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
}

.list-filter:focus {
  outline: none;
  border-color: var(--accent);
}

/* A filter holding something is worth seeing from across the table: it is the reason
   the list is short, and a person who forgot it is there reads a short list as an
   empty one. Marked by the script rather than by a selector on the value, because a
   select's current value is not an attribute and cannot be matched in CSS. */
.list-filter.is-active {
  border-color: var(--accent);
  color: var(--text);
}

/* A sortable heading is a control, so it says so on hover and takes focus from
   the keyboard like anything else that can be operated. */
th.is-sortable {
  cursor: pointer;
  user-select: none;
  position: relative;
}

th.is-sortable:hover {
  color: var(--text);
  background: var(--border);
}

th.is-sortable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* The arrow appears on the column actually being sorted. The others hold the
   space silently rather than showing a row of decorative arrows. */
/* The arrow is positioned inside the heading's existing padding and takes no space of
   its own. Reserving a column of space for it on every heading looked harmless and was
   not: eight sortable headings on the accounts table added a hundred pixels of width and
   pushed the table into a horizontal scroll it had never needed. */
th.is-sortable::after {
  content: '';
  position: absolute;
  right: 0.5rem;
  top: 50%;
  margin-top: -0.15rem;
  border-left: 0.28rem solid transparent;
  border-right: 0.28rem solid transparent;
  opacity: 0;
}

th.is-sortable[aria-sort='ascending']::after {
  border-bottom: 0.32rem solid var(--accent);
  opacity: 1;
}

th.is-sortable[aria-sort='descending']::after {
  border-top: 0.32rem solid var(--accent);
  opacity: 1;
}

th.is-sortable[aria-sort='ascending'],
th.is-sortable[aria-sort='descending'] {
  color: var(--text);
}

@media (max-width: 720px) {
  /* Stacked, so neither control is squeezed to a few characters wide.
     The flex basis has to be reset along with the direction: once the bar is a column,
     `flex: 1 1 18rem` is a request for eighteen rems of HEIGHT, and space-between then
     pushed the row selector most of a screen below the search box. */
  .list-controls {
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
  }
  .list-search { flex: 0 0 auto; }
  .list-search input { max-width: none; }
  .list-rows { justify-content: flex-end; }
}

/* ---------------------------------------------------------------------------
   A price edited where it is read.

   Every item's price is in the table, in a field, so the whole of a client's
   pricing can be taken in and corrected on one page rather than through a
   modal per item.
   --------------------------------------------------------------------------- */

.price-edit {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.15rem;
}

.price-currency {
  color: var(--text-muted);
  font-size: 0.88rem;
}

/* A fixed width, so the column does not resize as amounts are typed into it and
   shuffle every other column sideways while someone is reading. */
.price-input {
  width: 5rem;
  padding: 0.3rem 0.5rem;
  font-size: 0.88rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  margin: 0;
}

/* An empty field is a real answer -- the standard rate -- so its placeholder is
   the rate itself rather than a hint, and is dimmed only enough to read as unset. */
.price-input::placeholder {
  color: var(--text-muted);
  opacity: 0.7;
}

/* The term actually being paid on.
   Two prices sit side by side and only one of them is charged, so the live one is marked
   on the field itself rather than named in a column of its own: a reader running down a
   page of prices can see which figure is real without reading anything.
   A brighter gray than the resting border rather than white: the mark only has to be
   brighter than its neighbor to be read as the live one, and at full white it competed
   with the figures for attention. */
.price-input.is-term {
  border-color: var(--text-muted);
}

.price-input.is-term:focus {
  border-color: var(--accent);
}

/* The term a row is billed on.
   Sized to its longest option rather than left to the browser, which shrank it to a bare
   chevron in a narrow column and hid the very word it exists to show. */
.term-pick {
  /* Sized to its own longest option. Left at the form width it filled the cell and
     stretched the table past its container; shrunk to fit the cell it collapsed to a bare
     chevron with the word hidden. Sizing to content is the answer to both.
     The chevron gets less room than on a form field, which is where the width was going:
     2.4rem of padding for a 0.7rem mark is generous on a page and expensive in a table. */
  width: auto;
  min-width: 0;
  padding: 0.3rem 1.7rem 0.3rem 0.5rem;
  background-position: right 0.5rem center;
  font-size: 0.85rem;
}

.price-input.is-saving {
  opacity: 0.6;
}

/* Saved and refused both say so on the field itself. A price change with no
   acknowledgment leaves someone wondering whether it took, and re-typing it. */
.price-input.is-saved {
  border-color: var(--ok);
  box-shadow: 0 0 0 3px rgba(63, 166, 106, 0.18);
}

.price-input.is-error {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px rgba(207, 75, 75, 0.18);
}

/* ---------------------------------------------------------------------------
   Dropping a picture rather than hunting for it in a file dialog.

   The file input stays inside the zone rather than being replaced by it: a drop
   target alone is unusable by keyboard, and a file dialog is still the faster
   route for anyone who knows where the file is.
   --------------------------------------------------------------------------- */

.dropzone {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: var(--space-md);
  text-align: center;
  background: var(--bg);
  transition: border-color 140ms ease, background 140ms ease;
}

.dropzone-lead { margin: 0 0 var(--space-xs); font-weight: 600; }
.dropzone-or { margin: 0 0 var(--space-sm); font-size: 0.85rem; }
.dropzone input[type='file'] { max-width: 22rem; margin: 0 auto; }

/* While a file is over the zone. Both the border and the ground change, because a
   border alone is easy to miss under a dragged thumbnail. */
.dropzone.is-over {
  border-color: var(--accent);
  border-style: solid;
  background: rgba(212, 87, 31, 0.08);
}

/* While the upload is in flight, so a large picture on a slow line does not look
   like nothing happened. */
.dropzone.is-busy {
  opacity: 0.6;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .dropzone { transition: none; }
}

/* The calling code sits in front of the number it belongs to, sized to its content so a
   long country name in the list does not stretch the field beside it. */
.phone-row {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: var(--space-xs);
  align-items: center;
}

/* The option text is long -- flag, code, and country name -- but the closed control only
   needs to show the flag and the code, so the rest is allowed to be clipped. */
.phone-row select {
  width: 100%;
  text-overflow: ellipsis;
}

@media (max-width: 560px) {
  .phone-row { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------------------
   The country pickers.

   A native select can only hold text, so a flag inside one has to be an emoji,
   and whether an emoji flag waves is the operating system's decision. These
   draw their own rows, so the flags are flat SVGs at three by two everywhere.

   The select itself is still in the page and still the thing the form submits.
   --------------------------------------------------------------------------- */

.picker { position: relative; }

/* Present, submitted, and invisible. Not display:none, which would take it out
   of the form's own reckoning in some browsers. */
.picker-native {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Sized and painted as the text inputs beside it, so a row of fields reads as
   one row rather than as a control among inputs. */
.picker-button {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  width: 100%;
  padding: 0.7rem 2.4rem 0.7rem 0.85rem;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  font: inherit;
  font-size: 0.95rem;
  font-weight: 400;
  text-align: left;
  cursor: pointer;

  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%239aa5b1' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.9rem center;
  background-size: 0.7rem;
}

.picker-button:hover { background-color: var(--surface-raised); }

.picker-button:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(212, 87, 31, 0.18);
}

/* The label is allowed to be clipped rather than to widen the field: a long
   country name should not stretch the column it sits in. */
.picker-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Three by two, at every size, on every platform. A border because several
   flags are white at the edge and would otherwise bleed into the field. */
.flag {
  flex: 0 0 auto;
  width: 24px;
  height: 16px;
  border-radius: 2px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  object-fit: cover;
}

.picker-panel {
  position: absolute;
  z-index: 20;
  top: calc(100% + 0.35rem);
  left: 0;
  right: 0;
  min-width: 16rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
  padding: var(--space-xs);
}

.picker-search {
  width: 100%;
  margin-bottom: var(--space-xs);
  padding: 0.45rem 0.6rem;
  font-size: 0.9rem;
}

/* Ten rows and then scroll. The whole list is two hundred and thirty-nine long,
   which is why it filters as you type rather than asking anyone to scroll it. */
.picker-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 15rem;
  overflow-y: auto;
}

.picker-row {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 0.4rem 0.5rem;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.92rem;
}

.picker-row:hover,
.picker-row.is-active {
  background: var(--surface-raised);
}

/* The hidden attribute only sets display:none through the browser's own stylesheet, and
   any author rule with a display beats it -- so the display:flex above left every
   filtered-out row on screen while the code believed it had hidden them. */
.picker-row[hidden] { display: none; }

/* A key: one unbroken string, in the face that keeps its characters apart. It is not a word,
   and on a narrow screen it may wrap anywhere rather than push the table wider than the page. */
.key-text {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 0.8rem;
  overflow-wrap: anywhere;
}
/* Shown once, on its own, with room around it: the one string on the screen that matters. */
.key-reveal {
  display: block;
  margin-top: var(--space-md);
  padding: var(--space-sm) var(--space-md);
  border: 1px solid var(--border);
  border-radius: var(--radius, 6px);
  background: var(--surface-2, rgba(255, 255, 255, 0.04));
}
/* The protection choices at the foot of the service form, set apart from the way in above. */
.modal-guard {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--border);
}
.modal-guard .modal-hint { margin-top: var(--space-xs); }
.modal-guard .modal-field { margin-top: var(--space-md); }
/* The key table's middle column holds a long string and a sentence, both of which wrap inside
   their own cell rather than running on under the buttons beside them. The first column is
   sized to its longest label, and the tools to the one button each row has. */
.key-table th:first-child,
.key-table td:first-child { width: 14rem; }
.key-table td { white-space: normal; vertical-align: top; }
.key-table td.cell-action { white-space: nowrap; }

/* A finding that something is wrong, in a line of small print: the danger color, not a pill. */
.text-failing { color: var(--danger); }
/* How full a storage service is: a percentage over the two sizes it is made of. Centered, as
   the sizes in the column beside it are, so the two read as one pair of figures. */
.settings-table .cell-space,
.settings-table th.cell-space {
  width: 9rem;
  text-align: center;
}

/* Three counts under one caption: the newest, the weekly and the monthly copies to keep. */
.keep-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
}
.keep-cell {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.modal-field .keep-cell span.keep-label {
  font-size: 0.78rem;
  color: var(--text-muted);
  text-transform: none;
  letter-spacing: 0;
}

/* The parts a restore can put back, set in under "the whole account" they are an
   alternative to; one that cannot be chosen for where the copy is going is dimmed. */
.restore-what .check-line { margin-top: var(--space-xs); }
.restore-what .restore-part { padding-left: var(--space-md); }
.restore-what .is-off { opacity: 0.45; }

/* The offer to clear archives already copied off-site: a sentence and its button, under the
   table they belong to, with the same room above as the table's other companions. */
/* Set apart from the create and upload buttons below it by more than they are apart from
   each other, so the offer does not read as a third button of that row. */
.clear-offer { margin: var(--space-md) 0 var(--space-lg); }
.clear-offer .row-actions { margin-top: var(--space-sm); }

/* ------------------------------------------------------------------ the backup report */

.report-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}
.report-controls .modal-field { margin: 0; min-width: 14rem; }
.report-print { margin-left: auto; }
.report-head { margin: var(--space-md) 0; }
.report-head h2 { margin-bottom: var(--space-xs); }

/* Four figures across, two on a narrow screen: the month in the time it takes to glance. */
.report-figures {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-md);
  margin-bottom: var(--space-md);
}
@media (max-width: 56rem) {
  .report-figures { grid-template-columns: repeat(2, 1fr); }
}
.report-figure {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-md);
}
.report-value { font-size: 1.6rem; font-weight: 600; line-height: 1.2; }
.report-label { margin: var(--space-xs) 0; font-weight: 500; }
/* Sized to what the cells hold rather than fixed: seven columns of fixed width ran past the
   edge of a narrow page, and the account's name ran into the column beside it. */
.settings-table.report-table { table-layout: auto; }
.settings-table.report-table td,
.settings-table.report-table th { white-space: normal; }
.settings-table.report-table .cell-report,
.settings-table.report-table th.cell-report { text-align: center; }
/* A compound word is one word: "Off-Site" is never broken at its hyphen. */
.nowrap { white-space: nowrap; }
.report-notes { margin-top: var(--space-md); }
.report-notes p { line-height: 1.6; }

/* On paper the report is the agency's, not this product's: no navigation, no trail, no
   controls, no heading naming the account it was printed from -- and dark ink on white,
   whatever the screen was showing. */
@media print {
  .sidenav, .crumbs, .no-print, #app-notice, main > h1, body > footer, .site-footer,
  .report .list-count { display: none !important; }
  html, body, main, .report, .report-figure, .settings-table, .settings-table th, .settings-table td {
    background: #fff !important;
    color: #111 !important;
    box-shadow: none !important;
  }
  .muted, .report-notes { color: #444 !important; }
  .report-figure, .settings-table, .settings-table th, .settings-table td { border-color: #ccc !important; }
  /* The page is a grid with a column for the navigation; on paper there is no navigation,
     and the report takes the whole width rather than the navigation's narrow column. */
  body.has-sidenav { display: block !important; }
  main { margin: 0 !important; padding: 0 !important; max-width: none !important; width: auto !important; }
  .report-figures { grid-template-columns: repeat(4, 1fr); }
  .table-wrap { overflow: visible !important; }
  tr { break-inside: avoid; }
}

/* A place in the client's storage, heading the copies it holds: the service's name, then the
   folder and how many copies are there, on one line across the table. */
.copies-table tr.group-row td {
  padding-top: var(--space-md);
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--border);
}
.copies-table .group-name { font-weight: 600; margin-right: var(--space-sm); }
.copies-table .group-place { overflow-wrap: anywhere; }
/* A folder nothing is scheduled to any more. The mark sits after the folder's name, where
   the reader has just been told what it is, and the offer to stop listing an empty one sits
   after that again -- never crowding either. */
.copies-table .group-standing { margin-left: var(--space-sm); vertical-align: middle; }
.copies-table .group-forget {
  margin-left: var(--space-sm);
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  text-decoration: underline;
}
.copies-table .group-forget:hover { color: var(--accent-hover); }
/* Four marks: download, read back, restore, delete. The column is as wide as they are, and
   no wider, so the last is never cut off at the edge of the table. */
.settings-table.copies-table .cell-action,
.settings-table.copies-table th.cell-action {
  width: calc(4 * 15px + 3 * var(--space-sm) + 2 * var(--space-md));
}
.copies-table .cell-when,
.copies-table th.cell-when { width: 12rem; text-align: center; }

/* An empty page that says what would be here and how to begin, rather than only that
   nothing is: set in a panel so it reads as guidance, not as a table that failed to load. */
.empty-guide {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: var(--space-md) var(--space-md) var(--space-sm);
}
.empty-guide p { margin: 0 0 var(--space-sm); line-height: 1.6; }

/* What a status mark means, in a word beside it -- but only on a screen with no pointer that
   hovers. With a mouse, the mark's own tooltip says it; on a phone or tablet nothing does, and
   a colored dot alone is a color somebody has to already know the meaning of. The mark stays
   a button either way, and pressing it explains more. */
.dot-word { display: none; }
@media (hover: none) {
  .icon-button .dot-word {
    display: inline;
    margin-left: var(--space-xs);
    font-size: 0.75rem;
    white-space: nowrap;
  }
  .icon-button:has(.dot-word) { width: auto; padding: 0 var(--space-xs); }
  .row-tools { flex-wrap: wrap; row-gap: var(--space-xs); }
}

/* The storage services on a phone. Four fixed columns left the service's own name no width at
   all, and the space figure printed over it. Narrow, the table sizes itself to what it holds,
   and the thirty-day volume -- the column least needed at a glance -- steps aside; the space
   left in the account, which is the one that can stop a backup, stays. */
.space-inline { display: none; }
@media (max-width: 40rem) {
  .settings-table.destination-table { table-layout: auto; }
  .settings-table.destination-table .cell-size,
  .settings-table.destination-table th.cell-size,
  .settings-table.destination-table .cell-space,
  .settings-table.destination-table th.cell-space { display: none; }
  .settings-table.destination-table .space-inline { display: block; }
  .settings-table.destination-table .cell-action,
  .settings-table.destination-table th.cell-action { width: auto; }
  .settings-table.destination-table td:first-child { min-width: 8rem; }
}

/* The schedules on a phone. Five columns of fixed width printed over one another at this
   width; instead each schedule becomes a card, its figures one under another with the
   column's name above each, and its tools -- the status word beside the mark -- at the foot. */
@media (max-width: 40rem) {
  .settings-table.plan-table,
  .settings-table.plan-table tbody,
  .settings-table.plan-table tr,
  .settings-table.plan-table td { display: block; width: auto !important; }
  .settings-table.plan-table thead { display: none; }
  .settings-table.plan-table tr { padding: var(--space-sm) 0; border-bottom: 1px solid var(--border); }
  .settings-table.plan-table tr:last-child { border-bottom: none; }
  .settings-table.plan-table td {
    border: none;
    text-align: left !important;
    white-space: normal;
    padding: var(--space-xs) var(--space-md);
  }
  .settings-table.plan-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 2px;
    font-size: 0.7rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
  }
  .settings-table.plan-table .row-tools { justify-content: flex-start; }
}

/* ---------------------------------------------------------------------------
   The row of filters above a long administrative table.

   Choices a person clicks and choosers they pick from, on one line, with space
   around each so nothing touches its neighbor. The line wraps rather than
   scrolling: a filter that has gone off the side of the screen is a filter
   nobody will find.
   --------------------------------------------------------------------------- */

.filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  margin: var(--space-md) 0;
}
.filter-label {
  color: var(--text-muted);
  font-size: 0.85rem;
}
.filter-label:not(:first-child) { margin-left: var(--space-md); }
.filter-choice {
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: 0.85rem;
  text-decoration: none;
}
.filter-choice:hover { color: var(--text); border-color: var(--accent); }
.filter-choice.is-on {
  color: var(--text);
  border-color: var(--accent);
  background: var(--surface-raised);
}
.filter-select {
  /* Every select on this platform is full width, because every other one sits in a form
     field of its own. These sit in a row of filters and take the width of what they say. */
  width: auto;
  max-width: 16rem;
  padding: 0.3rem 2rem 0.3rem 0.6rem;
  background-position: right 0.6rem center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 0.85rem;
}

/* A link beside a heading, where the heading names a thing that has a page of its own.
   Sits at the far end of the section head, which is already a row with space between. */
.section-action {
  color: var(--accent);
  font-size: 0.85rem;
  text-decoration: none;
}
.section-action:hover { color: var(--accent-hover); text-decoration: underline; }

/* A count that is a count of something wrong. Zero stays the ordinary color, because a
   dashboard where the good news is colored reads as a dashboard full of alarms. */
.stat .value.value-warn { color: var(--warn); }

/* ---------------------------------------------------------------------------
   The report an agency hands to their own customer.

   The agency's mark above the heading, at a size that reads as a logo and never
   as a banner. The page has no navigation: whoever opens it was sent a link to
   one month of one report and has nothing else here to reach.
   --------------------------------------------------------------------------- */

.report-logo {
  display: block;
  max-height: 3rem;
  max-width: 16rem;
  margin-bottom: var(--space-sm);
}
.report-expiry { margin-top: var(--space-lg); }
body.shared-report main { padding-top: var(--space-lg); }

/* A sentence under a grid of fields, saying what they add up to. Set off from the last row
   of boxes rather than tight against it, and reading the full width of the card. */
.field-note {
  margin: var(--space-md) 0 0;
  max-width: var(--measure);
}


/* ---------------------------------------------------------------------------
   Dragon Websites only.

   Everything above is shared with Dragon Command Center. Only what that product has no
   use for belongs down here, so the two files stay diffable.
   --------------------------------------------------------------------------- */

/* The status circle a Tools column carries, per decision 0094.

   Four states, not three. A site that has never been checked is not the same as one checked
   and found clean, and a setting that does not exist on a site is not the same as one that
   exists and disagrees -- so "not applicable" gets its own gray rather than borrowing green.

   A circle rather than one of the pills above: down a column of forty sites the eye wants a
   shape it can scan, and a word it has to read at every row is slower than a color. The ring
   is what keeps them legible on the dark surface, where a flat dot at this size reads as a
   smudge. */
.dot {
  display: inline-block;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  vertical-align: middle;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.05);
}
.dot--clean { background: var(--ok); }
.dot--issues { background: var(--warn); }
.dot--disconnected { background: var(--danger); }
.dot--unknown { background: var(--donut-idle); }

/* Summary tiles above a list. Fractions stay clean at every width -- three across, then two,
   then one -- so a row is never left with a single stretched tile beside a gap. */
.tiles {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
}
@media (max-width: 62rem) { .tiles { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 40rem) { .tiles { grid-template-columns: 1fr; } }
.tile { min-width: 0; }
/* The shared stylesheet stacks cards by giving every card after the first a top margin. In
   a grid that margin is wrong twice over: the gap already separates the tiles, and the
   margin pushes each tile but the first down inside its own cell -- which left a row of
   three with the first tile both taller and higher than its neighbors. */
.tiles > .card + .card { margin-top: 0; }
.tile-label {
  margin: 0 0 var(--space-xs);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  font-weight: 600;
}
.tile-value { margin: 0; font-size: 1.25rem; font-weight: 650; letter-spacing: -0.01em; }
/* An address is long and is not a number, so it is set smaller and allowed to wrap. */
.tile-value.mono-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92rem;
  font-weight: 500;
  overflow-wrap: anywhere;
}

/* The first cell of a list row, which is the thing the row is about. */
td.name { font-weight: 600; }

/* Who is signed in, and the way out, at the foot of the navigation column.
   Dragon Command Center renders these as nav links because its sign-out is a link its client
   script intercepts. This product signs out with a form post, which needs no script and works
   with none -- so the button is made to read as the links above it rather than as a control. */
.sidenav-who {
  /* The same inset as .sidenav-list a above, so the name lines up with the navigation
     rather than sitting against the column's edge. The account block itself carries no
     horizontal padding, because Dragon Command Center's items get theirs from the links. */
  margin: 0;
  padding: var(--space-xs) var(--space-md);
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidenav-signout {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-left: 2px solid transparent;
  padding: var(--space-xs) var(--space-md);
  font: inherit;
  font-size: 0.92rem;
  color: var(--text-muted);
  cursor: pointer;
}
.sidenav-signout:hover { background: var(--surface-raised); color: var(--text); }
