/* Geodesix V0: the approvals platform's own stylesheet, one copy for all three V0 surfaces.

   Copied whole from v1-retailer/web/wireframe/style.css (the port plus the
   retailer's additions) with a V0 additions block at the end, and the same
   file sits in v0-retailer, v0-publisher and v0-admin. Drift between the
   three is corrected by copying again, never by editing one.

   This is a port of geodesix-approvals/src/app.css rather than a wireframe
   drawn to look like it. The approvals platform is built and live at
   geodesix.pages.dev, so the useful thing for this surface to show is what is
   actually on screen there: the same tokens, the same frame, the same card,
   table, badge, stat, form and login primitives. Nothing is shared between the
   two repos; drift is fine and is corrected by re-porting.

   Copied from v1-publisher/web/wireframe/style.css, which is the port itself:
   the two must stay in step, and drift between them is corrected by copying
   again rather than by editing both. docs/design-system.md records what the
   port contains and docs/ux-patterns.md the product decisions that come with
   it; those two files, not this one, are what a surface is brought onto the
   system from.

   Two local additions follow the port, at the end of the file: the V1 version
   banner, which every V1 page carries and the platform has no equivalent of,
   and the tab panel rule the wireframes' own tab controller needs. The fonts
   are named but not loaded, because a wireframe page opens from file:// with
   no remote assets; the stack falls back to the system face. */

/* Geodesix approvals. Ported from the main app's (app)/globals.css: the
   same tokens, the same frame (sidebar + topbar + content column), the
   same card, table, badge, stat, form and login primitives, so the two
   read as one product. Nothing is shared; drift is fine. */

:root {
  --pink-primary: #e91e8c;
  --pink-dark: #c4177a;
  --pink-press: #a51466;
  --pink-light: #f472b6;
  --red-accent: #e53935;
  --ink: #1a1a2e;
  --ink-2: #34344e;
  --muted: #5b626e;
  --faint: #69707d;
  --paper: #ffffff;
  --soft: #f9fafb;
  --soft-2: #f3f4f6;
  --pink-soft: #fdf2f8;
  --line: #e5e7eb;
  --line-strong: #d1d5db;
  --control-border: #848b97;
  --on-dark: #ffffff;
  --on-dark-muted: #c7c8d6;
  --ok: #15803d;
  --warn: #b45309;
  --bad: #b91c1c;
  --radius: 10px;
  --sidebar-w: 232px;
  --topbar-h: 72px;
}

* {
  box-sizing: border-box;
}
html,
body {
  margin: 0;
  padding: 0;
}
body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
  color: var(--ink);
  background: var(--soft);
  font-size: 14.5px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
a {
  color: inherit;
}
[hidden] {
  display: none !important;
}
h1,
h2,
h3,
h4 {
  font-weight: 700;
  letter-spacing: -0.4px;
  margin: 0;
}
p {
  margin: 0 0 10px;
}
:focus-visible {
  outline: 3px solid var(--pink-primary);
  outline-offset: 2px;
  border-radius: 4px;
}
.skiplink {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--on-dark);
  padding: 10px 16px;
  z-index: 100;
  border-radius: 0 0 8px 0;
}
.skiplink:focus {
  left: 0;
}
.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;
}
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0.01ms !important;
    /* The logo's flicker is `infinite`, and a 0.01ms infinite animation
       is still an animation. Capping the count is what actually leaves
       the mark as static navy dots. */
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0.01ms !important;
  }
}
.muted {
  color: var(--muted);
}
.small {
  font-size: 13px;
}
.nowrap {
  white-space: nowrap;
}
.link {
  color: var(--pink-dark);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.link:hover {
  color: var(--pink-press);
}

/* ---------- App frame ---------- */

.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100vh;
}
.sidebar {
  background: var(--paper);
  border-right: 1px solid var(--line);
  padding: 0 14px 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  padding: 4px 8px 14px;
}
.sidebar .brand-row {
  flex: none;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin: 0 -14px 10px;
  padding: 0 22px;
  border-bottom: 1px solid var(--line);
}
.sidebar .brand-row .brand {
  padding: 0;
  min-width: 0;
}
.brand .name {
  font-family: 'Sarabun', 'Inter', sans-serif;
  font-weight: 700;
  font-size: 17px;
  line-height: 1.15;
}
.brand .name small {
  display: block;
  font-family: 'Inter', sans-serif;
  font-weight: 500;
  font-size: 12px;
  color: var(--muted);
  letter-spacing: 0.3px;
}
.nav-section h4 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--faint);
  margin: 14px 8px 4px;
  font-weight: 600;
}
.nav-section + .nav-section h4 {
  margin-top: 30px;
}
.nav-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 8px;
  text-decoration: none;
  color: var(--ink-2);
  font-size: 13.5px;
}
.nav-item:hover {
  background: var(--soft);
}
.nav-item.active {
  background: var(--pink-soft);
  color: var(--pink-dark);
  font-weight: 600;
}
.nav-item .nav-count {
  margin-left: auto;
  font-size: 12px;
  font-weight: 700;
  line-height: 20px;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  text-align: center;
  border-radius: 999px;
  background: var(--pink-dark);
  color: var(--on-dark);
}
.main-col {
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.topbar {
  flex: none;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 28px;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
}
.topbar-start {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}
.topbar .title-wrap {
  min-width: 0;
}
.topbar .title-wrap h1 {
  font-size: 17px;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar-end {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}
.mobile-nav-toggle {
  display: none;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink);
  font-size: 15px;
  cursor: pointer;
}
.mobile-nav-toggle:hover {
  background: var(--soft);
}
.mobile-nav-close {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
}
.mobile-nav-close:hover {
  background: var(--soft);
  color: var(--ink);
}
.nav-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 30;
  border: 0;
  padding: 0;
  cursor: default;
  background: rgba(26, 26, 46, 0.35);
}
.content {
  padding: 24px 28px 48px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  max-width: 1240px;
  width: 100%;
}

/* ---------- User chip ---------- */

.userchip {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 12px 5px 6px;
  background: var(--paper);
  cursor: pointer;
  font-size: 13px;
}
.userchip .logo,
.userchip .logo-dots {
  flex: none;
}
.userchip .logo {
  border-radius: 50%;
  overflow: hidden;
}
.userchip .who {
  min-width: 0;
}
.entity-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
}
.userchip .who b {
  display: block;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chipmenu {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  min-width: 240px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(26, 26, 46, 0.12);
  padding: 6px;
  z-index: 40;
}
.chipmenu a,
.chipmenu button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  border-radius: 8px;
  text-decoration: none;
  font: inherit;
  font-size: 13px;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}
.chipmenu a:hover,
.chipmenu button:hover {
  background: var(--soft);
}
.cm-sep {
  border-top: 1px solid var(--line);
  margin: 6px 4px;
}
.chipmenu .cm-pick {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: none;
  font: inherit;
  font-size: 13px;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
/* A light line between accounts, so the list reads as rows rather than
   one block. The set is what is separated, so the first row has none. */
.chipmenu .cm-accounts .cm-pick + .cm-pick {
  border-top: 1px solid var(--line);
}
.chipmenu .cm-pick:hover {
  background: var(--soft);
}
/* The account being shown is the largest thing in the list and sits on
   its own ground, which is the whole marking; there are no checkmarks
   (Evan, 2026-09-03). */
.chipmenu .cm-pick.current {
  background: var(--soft-2);
  font-weight: 700;
  font-size: 14px;
  padding: 11px 10px;
}
.chipmenu .cm-pick.current:hover {
  background: var(--line);
}
.cm-who {
  padding: 8px 10px;
  font-size: 13px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* ---------- Cards, stats, sections ---------- */

.card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 20px;
}
.card > h3 {
  font-size: 15px;
  margin-bottom: 10px;
}
/* A second heading in the same card belongs to what follows it, not to
   the group above, so it opens a gap the heading's own 10px does not
   close (Evan, 2026-09-08). Notifications is three settings and one
   Save in one card. */
.card > .pickgroup + h3 {
  margin-top: 20px;
}
.card > h3 + p,
.card > .row-between + p,
.card > h3 + .muted,
.card > .row-between + .muted {
  margin-top: 0;
  margin-bottom: 14px;
}
.row-between {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.row-between h3 {
  font-size: 15px;
}
.section-h {
  font-size: 15px;
  margin-bottom: 0;
}
.statgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 14px;
}
.stat {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
}
.stat .label {
  color: var(--muted);
  font-size: 12px;
}
.stat .value {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.5px;
}
.stat .sub {
  color: var(--faint);
  font-size: 12px;
}
a.stat {
  display: block;
  color: inherit;
  text-decoration: none;
  transition: border-color 0.12s ease, background 0.12s ease;
}
a.stat:hover,
a.stat:focus-visible {
  border-color: var(--control-border);
  background: var(--soft);
}
/* .stat.urgent no longer colors its figure: no number is red on any V1
   page (Evan, 2026-09-22). The class stays as a marker only. */
.cardcols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 18px;
}
.narrow {
  max-width: 640px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
/* A form that runs the width of the page: the same column of cards as
   .narrow without the ceiling, for a form with a table in it (Evan,
   2026-09-08). */
.wide {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* ---------- Tables ---------- */

.tablewrap {
  overflow-x: auto;
}
table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
  background: var(--paper);
}
table.data th {
  text-align: left;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  font-weight: 600;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line-strong);
  white-space: nowrap;
}
table.data th button.th-sort {
  appearance: none;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: inherit;
  text-transform: inherit;
  letter-spacing: inherit;
  color: inherit;
  cursor: pointer;
}
table.data th button.th-sort:hover {
  color: var(--ink);
}
table.data th .sort-ind {
  font-size: 9px;
  color: var(--faint);
}
table.data th .sort-ind.active {
  color: var(--pink-dark);
}
table.data td {
  padding: 11px 12px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
table.data tr[data-href] {
  cursor: pointer;
}
table.data tr[data-href]:hover td {
  background: var(--soft);
}
table.data td.actions {
  text-align: right;
  white-space: nowrap;
}
/* Inside an expansion row's own nested table, the Placement column lines up
   left under its own left-aligned header, rather than the page-level
   .actions right-alignment meant for a lone trailing button (Evan,
   2026-09-22). */
tr.expansion table.data td.actions {
  text-align: left;
}
table.data td.num {
  white-space: nowrap;
}
/* A figure that opens something is a link without an underline; the
   underline comes back on hover so it still reads as one (Evan,
   2026-09-22). */
table.data td.num a {
  text-decoration: none;
}
table.data td.num a:hover,
table.data td.num a:focus-visible {
  text-decoration: underline;
}
/* The amount reads as the figure, the unit as its label under it. */
table.data td.rate-cell {
  white-space: nowrap;
}
table.data td.rate-cell .unit {
  display: block;
  font-size: 12px;
  color: var(--muted);
}
table.data .cell-empty {
  text-align: center;
  color: var(--muted);
  padding: 32px 12px;
}
/* The split under a status badge: "2 accepted, 1 waiting". */
.status-cell .split {
  display: block;
  margin-top: 3px;
  white-space: nowrap;
}
.row-between .status-cell {
  text-align: right;
}

/* ---------- Opening rows ----------
   The [+] column at the left of a table whose rows can open, and the
   full-width row that follows an opened one. The opened row loses its
   bottom rule so the two read as one. */
table.data th.expander,
table.data td.expander {
  width: 28px;
  padding-right: 0;
}
.card .tablewrap table.data td.expander + td,
.card .tablewrap table.data th.expander + th {
  padding-left: 8px;
}
.expand {
  appearance: none;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--control-border);
  border-radius: 6px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  display: inline-grid;
  place-items: center;
  cursor: pointer;
}
.expand:hover,
.expand[aria-expanded='true'] {
  background: var(--soft-2);
}
table.data tr.opened td {
  border-bottom: 0;
}
/* The panel's own cell, and only it: a descendant selector here also
   caught every td of the table inside the panel, which left those cells
   indented 58px under headers that were not (Evan, 2026-09-08). */
table.data tr.expansion > td {
  background: var(--soft);
  padding: 4px 12px 14px 50px;
  cursor: default;
}
.card .tablewrap table.data tr.expansion > td {
  padding-left: 58px;
}

/* ---------- Citations ----------
   A request's citations: waiting first, then accepted, then declined.
   The same list opens under a table row (compact) and sits on the
   detail page. */
.citations {
  display: grid;
  gap: 14px;
}
.cite-heading {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  font-weight: 600;
  margin: 0 0 6px;
}
.cite-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}
.cite {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 6px 14px;
  align-items: center;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper);
}
.cite.pending {
  border-color: var(--pink-light);
}
.cite-body {
  min-width: 0;
}
.cite-text {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink);
}
.cite-article {
  margin-top: 3px;
}
.cite-when {
  display: block;
  margin-top: 3px;
  font-size: 12px;
  color: var(--muted);
}
.cite-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}
.citations.compact .cite {
  padding: 8px 10px;
}
.citations.compact .cite-text {
  font-size: 13px;
}
table.data td.cite-cell {
  max-width: 520px;
}
table.data td.cite-cell .cite-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
table.data td.article-cell {
  min-width: 200px;
  max-width: 360px;
}
/* A whole article URL under a title or a passage. One line, cut with an
   ellipsis, because a real article URL is longer than any column it
   belongs in (Evan, 2026-09-08). */
.url-line {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--muted);
}
.url-line a {
  color: inherit;
}

/* A citations table inside an opened row: the list primitive on the
   panel's own ground, without the card edges it would get inside a
   card. */
tr.expansion table.data {
  background: transparent;
}
/* The row rule of table.data is #e5e7eb, which all but vanishes on the
   panel's own #f9fafb ground, so the citations ran together as one
   block of text (Evan, 2026-09-08). The stronger token, the one the
   header rule already uses, is the separator here. */
tr.expansion table.data td {
  border-bottom-color: var(--line-strong);
}
/* The last citation keeps its rule, unlike the last row of a table in a
   card, where the card's own edge closes the list. The panel has no
   edge, so without this a panel holding one citation showed no
   separator at all and its rows ran into the request under them (Evan,
   2026-09-08). */
tr.expansion table.data tbody tr:last-child td {
  border-bottom: 1px solid var(--line-strong);
}
table.data td.cite-cell .url-line {
  margin-top: 2px;
}
table.data td.num .unit {
  display: block;
  font-size: 12px;
  color: var(--muted);
}

/* The email preview: the message rendered by the sender's own template,
   in a frame so its styles and the app's cannot reach each other. */
.mailframe {
  display: block;
  width: calc(100% + 40px);
  margin: 4px -20px -18px;
  padding: 22px 20px;
  border: 0;
  border-top: 1px solid var(--line);
  background: var(--soft);
}
table.data td.article-cell .clamp {
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.card table.data {
  margin: 0 -20px -18px;
  width: calc(100% + 40px);
}
.card .tablewrap {
  margin: 0 -20px -18px;
}
.card .tablewrap table.data {
  margin: 0;
  width: 100%;
}
.card .tablewrap table.data th:first-child,
.card .tablewrap table.data td:first-child {
  padding-left: 20px;
}
.card .tablewrap table.data th:last-child,
.card .tablewrap table.data td:last-child {
  padding-right: 20px;
}
/* The outer table only: `>` all the way down, because the table inside
   an opened row is a `.card .tablewrap table.data` too, and this rule
   was taking the rule off its last citation as well (Evan,
   2026-09-08). */
.card > .tablewrap > table.data > tbody > tr:last-child > td {
  border-bottom: 0;
}
.entity {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.entity .ent-name {
  font-weight: 600;
  display: block;
  line-height: 1.25;
  white-space: nowrap;
}
.entity .ent-sub {
  display: block;
  color: var(--muted);
  font-size: 12px;
}
.entity .ent-sub a {
  color: inherit;
}
.logo {
  flex: none;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--paper);
  object-fit: contain;
  display: inline-grid;
  place-items: center;
}
.logo.placeholder {
  background: var(--soft-2);
  color: var(--muted);
  font-weight: 700;
}
/* The widest free-text column, and the one that has to yield when a
   Publisher column joins the row. Capped low enough that the actions at
   the end of the row stay on screen at a laptop width. */
.usage-cell {
  min-width: 150px;
  max-width: 260px;
  color: var(--ink-2);
}
.usage-cell .clamp {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- Badges ---------- */

.badge {
  display: inline-block;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 1px 9px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  background: var(--paper);
  white-space: nowrap;
}
.badge.dim {
  color: var(--muted);
  background: var(--soft);
  border-color: var(--line);
}
.badge.solid {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--on-dark);
}
.badge.pink {
  background: var(--pink-soft);
  border-color: var(--pink-light);
  color: var(--pink-dark);
}
.badge.ok {
  border-color: var(--ok);
  color: var(--ok);
}
.badge.bad {
  border-color: var(--bad);
  color: var(--bad);
}
/* Mixed: a citation request answered both ways. */
.badge.warn {
  border-color: var(--warn);
  color: var(--warn);
}

/* ---------- Filter bar and tabs ---------- */

.filterbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.filterbar input[type='search'] {
  flex: 0 1 280px;
  padding: 8px 12px;
}
.filterbar select {
  padding: 8px 10px;
}
.filterbar .actions {
  margin-left: auto;
  display: flex;
  gap: 8px;
}
/* The Filters disclosure: one button that opens a panel of selects, so a
   list has a single control rather than a row of loose dropdowns. */
details.filters {
  position: relative;
}
details.filters > summary {
  list-style: none;
  cursor: pointer;
  border: 1px solid var(--control-border);
  border-radius: 8px;
  padding: 7px 12px;
  background: var(--paper);
  font-size: 13.5px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
details.filters > summary::-webkit-details-marker {
  display: none;
}
details.filters > summary::before {
  content: '';
  width: 14px;
  height: 14px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'%3E%3Cpath d='M22 3H2l8 9.46V19l4 2v-8.54L22 3z'/%3E%3C/svg%3E")
    center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'%3E%3Cpath d='M22 3H2l8 9.46V19l4 2v-8.54L22 3z'/%3E%3C/svg%3E")
    center / contain no-repeat;
}
.filters-panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 30;
  min-width: 320px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(26, 26, 46, 0.12);
  padding: 14px;
}
.filters-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 4px;
}
.tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 14px;
}
.tabs button {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 8px 12px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}
.tabs button:hover {
  color: var(--ink);
}
.tabs button.active {
  color: var(--pink-dark);
  border-bottom-color: var(--pink-dark);
}

/* ---------- Buttons ----------

   A button's color says what it does, so the same kind of action looks
   the same everywhere (Evan, 2026-09-03; made one rule 2026-09-25):

     .btn.primary    pink fill: commits, creates or sends (Accept, Save,
                     Send, Add, Request, Use, Implement, Apply)
     .btn.secondary  pink outline: opens or changes one thing without
                     committing (View, Respond, Edit, Review, Update rate,
                     Deactivate as a link to Settings, Decline, Express
                     interest, Resend)
     .btn            gray outline: goes elsewhere or copies (View all,
                     Earnings, Copy, Reveal, Clear)
     .btn.cancel     gray fill: backs out (Cancel, Back to), and a
                     pagination arrow with nowhere to go
     .btn.danger     red outline: removes, revokes, blocks or deletes

   Placement: a card's header holds its navigation (View all, gray) or its
   own action (Edit, pink outline); a table row's actions sit in its last
   column, the alternative before the commit (Decline, Accept) and a
   removal last; a form ends Cancel at the left, the primary at the right.
   .quiet is kept as the old name for .secondary. */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--control-border);
  background: var(--paper);
  color: var(--ink);
  border-radius: 8px;
  padding: 8px 16px;
  font: inherit;
  font-weight: 600;
  font-size: 13.5px;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.btn:hover {
  background: var(--soft);
}
.btn.primary {
  border-color: var(--pink-dark);
  background: var(--pink-dark);
  color: #fff;
}
.btn.primary:hover {
  background: var(--pink-press);
  border-color: var(--pink-press);
}
.btn.secondary,
.btn.quiet {
  border-color: var(--pink-primary);
  background: var(--paper);
  color: var(--pink-dark);
}
.btn.secondary:hover,
.btn.quiet:hover {
  background: var(--pink-soft);
}
.btn.cancel {
  border-color: var(--line-strong);
  background: var(--soft-2);
  color: var(--muted);
}
.btn.cancel:hover {
  background: var(--line);
  color: var(--ink);
}
.btn.danger {
  background: var(--paper);
  color: var(--bad);
  border-color: var(--bad);
}
.btn.danger:hover {
  background: #fef2f2;
}
.btn.sm {
  padding: 5px 11px;
  font-size: 12.5px;
}
.btn:disabled {
  opacity: 0.6;
  cursor: default;
}
.btn-row {
  display: inline-flex;
  gap: 8px;
  align-items: center;
}
/* The confirm step asks a question and offers Cancel and Yes. The
   question sits above the two buttons so a longer one, "Accept the
   remaining 2?", does not widen the column it is in. */
.btn-row.confirming {
  flex-wrap: wrap;
  justify-content: flex-end;
  row-gap: 4px;
}
.btn-row.confirming .ask {
  flex-basis: 100%;
  text-align: right;
  white-space: nowrap;
}
.form-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 18px;
}
.form-actions .primary {
  margin-left: auto;
}

/* The admin's viewing-as bar, above the topbar and across the whole main
   column, the main app's .viewas (Evan, 2026-09-03). Ink, so there is no
   mistaking whose screen is on show. */
.viewas {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: var(--ink);
  color: var(--on-dark);
  padding: 10px 28px;
  font-size: 13px;
}
.viewas > span span {
  color: var(--on-dark-muted);
  margin-left: 8px;
}

/* A way back that sits above the thing it leaves, rather than a button
   crowding the user chip in the topbar (Evan, 2026-09-03). */
.backlink {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 14px;
  color: var(--muted);
  font-size: 13.5px;
  font-weight: 600;
  text-decoration: none;
}
.backlink:hover {
  color: var(--pink-dark);
}
.backlink .arrow {
  font-size: 15px;
  line-height: 1;
}

/* A page's own action, right aligned above its content. */
.pageactions {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 14px;
}

/* ---------- Forms ---------- */

input[type='text'],
input[type='url'],
input[type='email'],
input[type='number'],
input[type='search'],
input[type='date'],
input[type='file'],
input[type='password'],
input:not([type]),
select,
textarea {
  padding: 9px 12px;
  border: 1px solid var(--control-border);
  border-radius: 8px;
  font: inherit;
  color: var(--ink);
  background: var(--paper);
  max-width: 100%;
  min-width: 0;
  width: 100%;
}
/* A date input's calendar icon and a file input's own "Choose file" button
   stay native; the box around either one is styled to match so a form does
   not visibly switch systems partway down. */
input[type='date'] {
  color-scheme: light;
}
input[type='file'] {
  font-size: 13px;
}
/* A URL field paired with a fetch action, used when adding an article: the
   field takes the row, the button holds its width and never wraps under it. */
.inline-fetch {
  display: flex;
  gap: 8px;
  align-items: center;
}
.inline-fetch input {
  flex: 1;
  min-width: 0;
}
.inline-fetch .btn {
  flex: none;
  white-space: nowrap;
}
textarea {
  resize: vertical;
}
.formgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 0 12px;
}
@media (min-width: 520px) {
  .formgrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
/* An identity form (a person's own name, email, password) reads better as
   one field per row than as two across; a bulk data-entry form keeps the
   default two columns (Evan, 2026-09-22). */
.formgrid.stacked {
  grid-template-columns: 1fr;
}
.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-bottom: 14px;
  min-width: 0;
}
.field label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
}
.field .hint {
  color: var(--muted);
  font-size: 12px;
}
.field.check {
  flex-direction: row;
  align-items: center;
  gap: 8px;
}
.field.check input {
  width: auto;
}
/* One field's complaint, under the control that caused it. */
.field-error {
  color: var(--bad);
  font-size: 12px;
  font-weight: 600;
}
.form-error {
  background: #fef2f2;
  border: 1px solid #fecaca;
  color: var(--bad);
  border-radius: 8px;
  padding: 10px 14px;
  font-size: 13px;
}
.notice {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid var(--ok);
  border-left-width: 4px;
  background: #f0fdf4;
  color: #14532d;
  border-radius: 8px;
  padding: 11px 14px;
  font-size: 13.5px;
  font-weight: 600;
}
.notice.bad {
  border-color: var(--bad);
  background: #fef2f2;
  color: var(--bad);
}
.notice.info {
  border-color: var(--line-strong);
  background: var(--soft-2);
  color: var(--ink-2);
}
.notice span {
  flex: 1;
}
.notice a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.notice button {
  border: 0;
  background: none;
  font: inherit;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 0 2px;
}
.notice button:hover {
  color: var(--ink);
}

/* ---------- Publisher picker ----------
   The grant list on Access: publishers grouped by organization, each
   group with a whole-company shortcut. A fieldset per group, because
   that is what a group of related checkboxes is, and a screen reader
   then reads the company name before each title. */

.picker {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: 14px;
  margin-top: 4px;
}
.pickgroup {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 14px 12px;
  margin: 0;
  min-width: 0;
}
.pickgroup + .pickgroup,
.pickgroup + .field {
  /* Stacked groups, and the field under them, sit apart rather than
     touching (2026-09-28, Evan: "These are too cramped together"). */
  margin-top: 16px;
}
.cardcols > .pickgroup + .pickgroup,
.digest > .pickgroup + .pickgroup,
.acctmail-body > .pickgroup + .pickgroup {
  /* A grid or flex column already spaces its children with its own gap. */
  margin-top: 0;
}
.pickgroup legend {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 0 6px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--faint);
}
.pickitem {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 5px 0;
  font-size: 13.5px;
  font-weight: 400;
  color: var(--ink);
  cursor: pointer;
}
.pickitem input {
  width: auto;
  flex: none;
  margin: 0;
}
/* One column, for a list read top to bottom: the citation picker on a
   request, and the notification choices. */
.picker.stack {
  grid-template-columns: 1fr;
}
.picker.stack .pickitem {
  align-items: flex-start;
}
.picker.stack .pickitem input {
  margin-top: 3px;
}
.pickitem .cite-text {
  font-size: 13.5px;
}
.pickgroup legend .badge {
  text-transform: none;
  letter-spacing: 0;
}

/* Two named choices with their price, standing in for a plain radio list
   where the price is the thing being decided between (select-citation.html,
   Evan, 2026-09-21). */
.pricepick {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 10px;
  margin-top: 4px;
}
.pricebtn {
  display: flex;
  flex-direction: column;
  gap: 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px 14px;
  cursor: pointer;
}
.pricebtn input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.pricebtn-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
}
.pricebtn-price {
  font-size: 13px;
  color: var(--muted);
}
.pricebtn.active {
  border-color: var(--pink-dark);
  background: var(--soft);
}
.linkbtn {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--pink-dark);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.linkbtn:hover {
  color: var(--pink-press);
}
/* Several small badges in one cell, wrapping rather than stretching the
   column: an editor with four titles is a normal row. */
.badges-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  max-width: 320px;
}

/* ---------- Detail page ---------- */

/* A field list: one field per row, the label then its value, on every V1
   page. The wrapping div is flattened so each pair takes its own grid row
   rather than two pairs sitting side by side as stacked label over value.
   No rule between rows, tight spacing, and the label's colon comes from here
   rather than the markup, so no page can drop it (Evan, 2026-09-22).
   `.kv.rows` is the older name for the same thing and still works. */
.kv {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px 16px;
  align-items: baseline;
  margin: 0;
  font-size: 13.5px;
}
.kv > div {
  display: contents;
}
.kv dt {
  color: var(--muted);
  font-weight: 600;
  font-size: 12.5px;
}
.kv dt::after {
  content: ':';
}
.kv dd {
  margin: 0;
  min-width: 0;
}
.kv dd.big {
  font-size: 20px;
  font-weight: 700;
  letter-spacing: -0.3px;
}
.timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
}
.timeline li {
  display: grid;
  grid-template-columns: 10px 1fr;
  gap: 10px;
  align-items: start;
  font-size: 13.5px;
}
.timeline li::before {
  content: '';
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--line-strong);
  margin-top: 6px;
}
.timeline li.accepted::before {
  background: var(--ok);
}
.timeline li.declined::before {
  background: var(--bad);
}
.timeline li.reopened::before {
  background: var(--warn);
}
.timeline .when {
  color: var(--muted);
  font-size: 12px;
  display: block;
}
.confirm {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  background: var(--soft-2);
  border-radius: 8px;
  padding: 10px 12px;
  font-size: 13.5px;
}
.confirm span {
  flex: 1 1 200px;
}

/* ---------- Empty ---------- */

.empty {
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  padding: 36px 24px;
  text-align: center;
  color: var(--muted);
  background: var(--paper);
}
.empty b {
  display: block;
  color: var(--ink-2);
  margin-bottom: 4px;
}

/* ---------- Login ---------- */

.login-wrap {
  min-height: 100vh;
  display: grid;
  grid-template-rows: 1fr auto;
  background:
    radial-gradient(60% 50% at 85% -10%, rgba(233, 30, 140, 0.08), transparent),
    var(--soft);
}
.login-center {
  display: grid;
  place-items: center;
  padding: 24px;
}
.login-card {
  width: 100%;
  max-width: 400px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 32px;
  box-shadow: 0 20px 50px rgba(26, 26, 46, 0.08);
}
/* The mark and the word Geodesix on one line, the same on all three V1
   logins, with no audience name after it (Evan, 2026-09-22). */
.login-card .auth-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 0 0 28px;
}
.login-card .auth-brand h1 {
  font-family: 'Sarabun', 'Inter', sans-serif;
  font-size: 24px;
  line-height: 1;
}
.login-card form > .btn {
  width: 100%;
  justify-content: center;
  padding: 11px 16px;
  margin-bottom: 4px;
}
.login-card .sent {
  text-align: center;
  font-size: 14px;
  color: var(--ink-2);
}
.login-card .sent b {
  display: block;
  margin-bottom: 4px;
}
.login-card .after {
  text-align: center;
  margin: 24px 0 0;
  font-size: 13.5px;
}
/* "Not someone@example.com?" over the link that changes it. The address
   wraps rather than pushing the card wide, since a long one is common. */
.login-card .after .muted {
  display: block;
  font-size: 13px;
  overflow-wrap: anywhere;
}
.login-card .after .muted + br {
  display: none;
}
.login-card .after a,
.login-card .after button {
  display: inline-block;
  padding: 8px 12px;
  color: var(--pink-dark);
  background: none;
  border: 0;
  font: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ---------- Footer ---------- */

.appfooter {
  margin-top: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 4px 24px;
  padding: 16px 28px 20px;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 13px;
}
.appfooter nav {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}
.appfooter a {
  color: var(--muted);
  text-decoration: none;
  padding: 6px 0;
}
.appfooter a:hover,
.appfooter a:focus-visible {
  color: var(--ink);
  text-decoration: underline;
}


/* ---------- Logo flicker ----------
   Ported from the main app's globals.css. One 12 second cycle, four
   pairs lighting up in turn at 0, 16, 32 and 48 percent, each pair one
   red dot and one gray, so at most two of the eight are colored at any
   moment and the mark reads as itself throughout. */

.logo-dots {
  flex: none;
}
.logo-dot {
  transition: fill 0.2s ease;
}
.dot-a {
  animation: flickerA 12s infinite;
}
.dot-b {
  animation: flickerB 12s infinite;
}
.dot-c {
  animation: flickerC 12s infinite;
}
.dot-d {
  animation: flickerD 12s infinite;
}
.dot-e {
  animation: flickerE 12s infinite;
}
.dot-f {
  animation: flickerF 12s infinite;
}
.dot-g {
  animation: flickerG 12s infinite;
}
.dot-h {
  animation: flickerH 12s infinite;
}
@keyframes flickerA {
  0%,
  2% {
    fill: #1a1a2e;
  }
  4%,
  10% {
    fill: #e53935;
  }
  12%,
  100% {
    fill: #1a1a2e;
  }
}
@keyframes flickerE {
  0%,
  2% {
    fill: #1a1a2e;
  }
  4%,
  10% {
    fill: #9ca3af;
  }
  12%,
  100% {
    fill: #1a1a2e;
  }
}
@keyframes flickerC {
  0%,
  16% {
    fill: #1a1a2e;
  }
  18%,
  26% {
    fill: #9ca3af;
  }
  28%,
  100% {
    fill: #1a1a2e;
  }
}
@keyframes flickerF {
  0%,
  16% {
    fill: #1a1a2e;
  }
  18%,
  26% {
    fill: #e53935;
  }
  28%,
  100% {
    fill: #1a1a2e;
  }
}
@keyframes flickerB {
  0%,
  32% {
    fill: #1a1a2e;
  }
  34%,
  42% {
    fill: #e53935;
  }
  44%,
  100% {
    fill: #1a1a2e;
  }
}
@keyframes flickerG {
  0%,
  32% {
    fill: #1a1a2e;
  }
  34%,
  42% {
    fill: #9ca3af;
  }
  44%,
  100% {
    fill: #1a1a2e;
  }
}
@keyframes flickerD {
  0%,
  48% {
    fill: #1a1a2e;
  }
  50%,
  58% {
    fill: #9ca3af;
  }
  60%,
  100% {
    fill: #1a1a2e;
  }
}
@keyframes flickerH {
  0%,
  48% {
    fill: #1a1a2e;
  }
  50%,
  58% {
    fill: #e53935;
  }
  60%,
  100% {
    fill: #1a1a2e;
  }
}

/* ---------- Mobile ---------- */

@media (max-width: 880px) {
  .app {
    grid-template-columns: 1fr;
  }
  .mobile-nav-toggle {
    display: flex;
  }
  .nav-backdrop {
    display: block;
  }
  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 40;
    width: min(280px, 82vw);
    height: 100vh;
    height: 100dvh;
    padding-bottom: calc(18px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-100%);
    visibility: hidden;
    transition:
      transform 0.2s ease,
      visibility 0s linear 0.2s;
  }
  .sidebar.open {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.2s ease;
  }
  .viewas {
    padding: 10px 16px;
  }
  .topbar {
    padding: 0 16px;
  }
  .userchip .who {
    display: none;
  }
  .userchip {
    padding: 4px;
  }
  .content {
    padding: 16px 16px 40px;
  }
  .appfooter {
    padding: 12px 16px 16px;
    font-size: 12px;
    gap: 4px 14px;
  }
  .appfooter nav {
    gap: 14px;
  }
  table.data tr.expansion > td,
  .card .tablewrap table.data tr.expansion > td {
    padding-left: 16px;
  }
  .cite {
    grid-template-columns: 1fr;
  }
  .cite-side {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
  }
}


/* ---------- Responsive ----------

   What keeps a page inside the viewport at any width, and a control on one
   line wherever the row has room for it. A grid item's minimum size is its
   content unless told otherwise, so a table inside a card inside a grid used
   to push the card past the edge of the page instead of scrolling inside its
   own .tablewrap; the min-width rules are what let the scroll wrapper work. */

.content > *,
.tabpanel > *,
.grid > *,
.cardcols > *,
.statgrid > *,
.picker > *,
.formgrid > *,
.card {
  min-width: 0;
}
/* A detail page's main column beside a side column: the side column never
   drops under 280px, and the two stack once the row cannot hold both, which
   with the sidebar on screen is already the case under 1000px. */
.detail-cols {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
  gap: 22px;
  align-items: start;
}
.detail-cols > * {
  min-width: 0;
}
@media (max-width: 1000px) {
  .detail-cols {
    grid-template-columns: minmax(0, 1fr);
  }
}
/* The foot of a list wraps as a row before either half wraps inside. */
.pagination {
  flex-wrap: wrap;
}
.pagination > * {
  white-space: nowrap;
}
.pagination .pages {
  flex: none;
  margin-left: auto;
}
/* The search box gives way first, so the control beside it stays on the
   same line down to a phone; the controls themselves never shrink. */
.filterbar input[type='search'] {
  flex: 1 1 80px;
  min-width: 0;
  max-width: 280px;
}
/* A primary button on the bar can carry a short label for a phone
   (span.long and span.short, the date range's idiom), so the search box,
   Filters and the button share one row down to 360px. */
.filterbar .btn .short {
  display: none;
}
@media (max-width: 600px) {
  .filterbar .btn .long {
    display: none;
  }
  .filterbar .btn .short {
    display: inline;
  }
}
.filterbar select,
.filterbar .btn,
.filterbar details.filters > summary {
  flex: none;
}
.filters-panel {
  max-width: calc(100vw - 32px);
}
/* On a phone the panel drops under the whole bar rather than hanging off the
   Filters button, where it would run past the right edge. */
@media (max-width: 600px) {
  .filterbar {
    position: relative;
  }
  .filterbar details.filters {
    position: static;
  }
  .filterbar .filters-panel {
    left: 0;
    right: 0;
    min-width: 0;
    max-width: none;
  }
}
/* A key or a snippet written inline breaks rather than widening the page. */
code {
  overflow-wrap: anywhere;
}
/* A cell holding one link or one bold figure stays on a line: the table
   scrolls inside its wrapper before a name breaks across two lines. */
table.data td > a:only-child,
table.data td > b:only-child {
  white-space: nowrap;
}
/* The one line article URL under a quote takes the cell's width instead of
   setting it, so a quote column gives way to the columns beside it. */
table.data td .url-line {
  width: 0;
  min-width: 100%;
}
.viewas {
  flex-wrap: wrap;
}
/* The drawer's close button only exists while the nav is a drawer. */
.mobile-nav-close {
  display: none;
}
@media (max-width: 880px) {
  .mobile-nav-close {
    display: flex;
  }
  body.nav-open {
    overflow: hidden;
  }
}

/* ---------- Phone sizing ----------

   Under 720px every control meets the platform target sizes: Apple's Human
   Interface Guidelines ask for 44 by 44pt and Material for 48 by 48dp with
   8dp between targets, so the primary controls are 48px tall and the small
   ones 44px. Body text goes to 16px (Apple's minimum is 11pt, its body
   17pt; Material body is 14 to 16sp), and every field is 16px so iOS does
   not zoom the page when one is focused. Focus rings, contrast and reduced
   motion are the port's own and already meet WCAG AA. */

@media (max-width: 720px) {
  body {
    font-size: 16px;
  }
  .btn {
    min-height: 48px;
    padding: 10px 18px;
    font-size: 15px;
  }
  .btn.sm {
    min-height: 44px;
    padding: 8px 14px;
    font-size: 14px;
  }
  .nav-item {
    min-height: 48px;
    padding: 12px 12px;
    font-size: 16px;
  }
  .nav-section h4 {
    font-size: 12.5px;
  }
  .mobile-nav-toggle,
  .mobile-nav-close {
    width: 48px;
    height: 48px;
    font-size: 16px;
  }
  .userchip {
    min-height: 44px;
    padding: 4px 8px;
  }
  .chipmenu a,
  .chipmenu button,
  .chipmenu .cm-pick {
    min-height: 48px;
    display: flex;
    align-items: center;
    font-size: 15px;
  }
  .tabs button {
    min-height: 48px;
    font-size: 15px;
  }
  .seg,
  .dr-opt {
    min-height: 44px;
    padding: 10px 8px;
    font-size: 14px;
    flex: 1 1 auto;
  }
  .segbar,
  .daterange {
    display: flex;
    width: 100%;
  }
  .daterange .dr-panel {
    left: 0;
    right: auto;
    max-width: 100%;
  }
  input[type='text'],
  input[type='url'],
  input[type='email'],
  input[type='number'],
  input[type='search'],
  input[type='date'],
  input[type='file'],
  input[type='password'],
  input:not([type]),
  select,
  textarea,
  .filterbar select,
  table.data select {
    font-size: 16px;
    min-height: 48px;
  }
  input[type='checkbox'],
  input[type='radio'] {
    width: 24px;
    height: 24px;
    min-height: 0;
  }
  .pickitem,
  .field.check,
  .switchrow,
  .selectcard {
    min-height: 48px;
  }
  .switch .track,
  .switchrow .sw {
    width: 52px;
    height: 32px;
  }
  .switch .track::after,
  .switchrow .sw::after {
    width: 26px;
    height: 26px;
    top: 3px;
    left: 3px;
  }
  .switch.on .track::after,
  .switchrow.on .sw::after {
    left: 23px;
  }
  .expand {
    width: 44px;
    height: 44px;
    font-size: 20px;
  }
  table.data th.expander,
  table.data td.expander {
    width: 48px;
  }
  .backlink,
  .linkbtn,
  .appfooter a,
  .pagination .pages > * {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  .field label {
    font-size: 14px;
  }
  .badge {
    font-size: 13px;
    padding: 3px 11px;
  }
  .stat .label,
  .stat .sub {
    font-size: 13px;
  }
  table.data {
    font-size: 15px;
  }
  table.data td {
    padding: 13px 12px;
  }
  .kv,
  .cite-text {
    font-size: 15px;
  }
  .url-line {
    font-size: 13px;
  }
  .kv dd {
    overflow-wrap: anywhere;
  }
  /* Selects keep their 48px even where the port resets them (filter bars and
     tables), which is why these are written from main.content. */
  main.content select,
  main.content .filterbar select,
  main.content table.data select {
    min-height: 48px;
  }
  /* A sortable header is a 44px target without the header growing: the
     button's padding is taken back by its margin. */
  table.data th {
    padding: 14px 12px;
  }
  table.data th button.th-sort {
    min-height: 44px;
    margin: -14px 0;
    align-items: center;
  }
  table.data th button.th-sort {
    min-width: 44px;
  }
  details.filters > summary {
    min-height: 48px;
  }
  /* Every text link in the content column (a name in a row, a count, a
     domain, an article URL, a link in running text) gets a 44px hit area
     from an invisible box drawn around it, so the line it sits on does not
     move and short links like a count of 2 are still a full target. */
  main.content a:not(.btn):not(.stat):not(.backlink):not(.nav-item):not(.seg):not(.dr-opt) {
    position: relative;
  }
  main.content a:not(.btn):not(.stat):not(.backlink):not(.nav-item):not(.seg):not(.dr-opt)::after {
    content: '';
    position: absolute;
    inset: -16px -18px;
  }
  .appfooter a {
    padding: 12px 10px;
    margin: 0 -10px;
  }
  .appfooter nav {
    gap: 28px;
  }
}

/* ---------- Tables on a phone and tablet ----------

   A table stays a table at every width. Under 880px it scrolls sideways
   inside its wrapper with the first column held in place (sticky), so the
   row's name stays readable while the figures scroll under it, headers
   included. At the left edge the table reads as it does on the desktop: the
   [+] column, the logo and the name. Once the table has scrolled (nav.js
   marks the wrapper scrolled) the held part collapses to stay narrow: the
   [+] column folds away and a tap on the row opens it instead, and where
   the first cell carries a logo, an avatar or a product thumbnail (an
   .entity; nav.js marks such a wrapper img-first) the name beside the image
   folds away too and the header over it blanks, so only the image is held.
   Scrolling back to the edge unfolds them. The folds are max-width and
   opacity transitions, so the columns slide rather than jump. A first cell
   with no image (a quote, an article, a placement name) is held whole.
   nav.js also marks the wrapper can-scroll and at-end, so the right edge
   fades while there is more to the right and the held column takes a
   shadow once the rest has moved. Nothing here scrolls the page. */

/* A table wider than its card scrolls at any width; the right edge fades
   while there is more to the right. */
.tablewrap.can-scroll:not(.at-end) {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 32px), transparent);
}

@media (max-width: 880px) {
  .tablewrap {
    position: relative;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
  }
  .tablewrap table.data th:first-child,
  .tablewrap table.data td:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--paper);
  }
  /* The [+] column shows at the left edge and folds away once the table
     has scrolled; the row itself opens on a tap at any time (nav.js). */
  .tablewrap table.data th.expander,
  .tablewrap table.data td.expander {
    width: 48px;
    min-width: 48px;
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--paper);
    transition: min-width 0.25s, width 0.25s, padding 0.25s;
  }
  .tablewrap table.data td.expander .expand {
    overflow: hidden;
    transition: width 0.25s, opacity 0.25s, border-width 0.25s;
  }
  .tablewrap.scrolled table.data th.expander,
  .tablewrap.scrolled table.data td.expander {
    width: 0;
    min-width: 0;
    padding-left: 0;
    padding-right: 0;
  }
  .tablewrap.scrolled table.data td.expander .expand {
    width: 0;
    opacity: 0;
    border-width: 0;
  }
  .tablewrap table.data th.expander + th,
  .tablewrap table.data td.expander + td {
    position: sticky;
    left: 48px;
    z-index: 1;
    background: var(--paper);
    transition: left 0.25s, padding 0.25s;
  }
  .tablewrap.scrolled table.data th.expander + th,
  .tablewrap.scrolled table.data td.expander + td {
    left: 0;
    padding-left: 12px;
  }
  .tablewrap table.data tbody tr.opens {
    cursor: pointer;
  }
  .tablewrap table.data tr.expansion > td {
    position: static;
  }
  .tablewrap.scrolled table.data th:first-child:not(.expander),
  .tablewrap.scrolled table.data td:first-child:not(.expander),
  .tablewrap.scrolled table.data th.expander + th,
  .tablewrap.scrolled table.data td.expander + td {
    box-shadow: 6px 0 8px -6px rgba(26, 26, 46, 0.18);
  }
  .tablewrap table.data td:first-child .entity .ent-name,
  .tablewrap table.data td.expander + td .entity .ent-name {
    white-space: nowrap;
  }
  /* The held column keeps enough room to read: a quote or an article title
     gets 200px, anything else 120px. */
  .tablewrap table.data td.cite-cell:first-child,
  .tablewrap table.data td.article-cell:first-child,
  .tablewrap table.data td.expander + td.cite-cell {
    min-width: 200px;
  }
  .tablewrap table.data td:first-child:not(.expander):not(.cite-cell):not(.article-cell):not(.num),
  .tablewrap table.data td.expander + td:not(.cite-cell) {
    min-width: 120px;
  }
  /* A first cell with a logo, an avatar or a thumbnail: the name beside the
     image folds away once the table has scrolled, and the header over it
     with it, so the image alone is held. */
  .tablewrap.img-first.scrolled > table.data > tbody > tr > td:first-child:not(.expander):not(.cite-cell):not(.article-cell):not(.num),
  .tablewrap.img-first.scrolled > table.data > tbody > tr > td.expander + td:not(.cite-cell) {
    min-width: 0;
    width: 1%;
  }
  .tablewrap.img-first > table.data > tbody > tr > td:first-child .entity > :not(.logo):not(.imgph),
  .tablewrap.img-first > table.data > tbody > tr > td.expander + td .entity > :not(.logo):not(.imgph),
  .tablewrap.img-first > table.data > thead th:first-child .th-sort,
  .tablewrap.img-first > table.data > thead th.expander + th .th-sort {
    display: inline-block;
    vertical-align: middle;
    max-width: 240px;
    overflow: hidden;
    white-space: nowrap;
    transition: max-width 0.25s, opacity 0.2s;
  }
  .tablewrap.img-first.scrolled > table.data > tbody > tr > td:first-child .entity > :not(.logo):not(.imgph),
  .tablewrap.img-first.scrolled > table.data > tbody > tr > td.expander + td .entity > :not(.logo):not(.imgph),
  .tablewrap.img-first.scrolled > table.data > thead th:first-child .th-sort,
  .tablewrap.img-first.scrolled > table.data > thead th.expander + th .th-sort {
    max-width: 0;
    opacity: 0;
  }
  .tablewrap.img-first > table.data > tbody > tr > td:first-child .entity,
  .tablewrap.img-first > table.data > tbody > tr > td.expander + td .entity {
    transition: gap 0.25s;
  }
  .tablewrap.img-first.scrolled > table.data > tbody > tr > td:first-child .entity,
  .tablewrap.img-first.scrolled > table.data > tbody > tr > td.expander + td .entity {
    gap: 0;
  }
  /* An opened row's panel (the citations under a request) stays in view
     while the table behind it scrolls: it is held at the left edge and
     sized to the wrapper (nav.js keeps --wrapw current). Its table reads as
     a stack, one block per citation with the quote, what it refers to and
     its status in turn, since three columns do not fit beside a phone's
     held column. */
  .tablewrap table.data tr.expansion > td,
  .card .tablewrap table.data tr.expansion > td {
    padding: 4px 12px 12px;
  }
  .tablewrap table.data tr.expansion > td > * {
    position: sticky;
    left: 0;
    width: calc(var(--wrapw, 100vw) - 24px);
    max-width: 100%;
  }
  .tablewrap table.data tr.expansion table.data > thead {
    display: none;
  }
  .tablewrap table.data tr.expansion table.data {
    display: block;
  }
  .tablewrap table.data tr.expansion table.data > tbody,
  .tablewrap table.data tr.expansion table.data > tbody > tr,
  .tablewrap table.data tr.expansion table.data > tbody > tr > td {
    display: block;
    position: static;
    min-width: 0;
    box-shadow: none;
  }
  .tablewrap.scrolled table.data tr.expansion table.data > tbody > tr > td {
    box-shadow: none;
  }
  .tablewrap table.data tr.expansion table.data > tbody > tr {
    padding: 10px 0;
    border-bottom: 1px solid var(--line);
  }
  .tablewrap table.data tr.expansion table.data > tbody > tr:last-child {
    border-bottom: 0;
  }
  .tablewrap table.data tr.expansion table.data > tbody > tr > td {
    padding: 3px 0;
    border: 0;
    background: transparent;
    text-align: left;
    white-space: normal;
  }
  .tablewrap table.data tr.expansion table.data > tbody > tr > td.cite-cell {
    max-width: none;
  }
}

/* ---------- Wireframe additions ----------

   Everything above is the port. Everything below exists because this is a
   wireframe rather than the running app, and should be left behind when the
   port is taken anywhere else. */

/* The V1 banner, drawn by nav.js on every page of the surface. */
.versionbar {
  padding: 9px 28px;
  background: var(--soft-2);
  border-bottom: 1px solid var(--line);
}
.versionbar .vb-txt b {
  display: block;
  font-size: 13px;
}
.versionbar .vb-txt span {
  display: block;
  font-size: 11.5px;
  color: var(--muted);
}

/* A tabbed page draws one panel at a time. The bar is .tabs[data-tabs] and each
   panel is .tabpanel[data-tab]; nav.js switches them by setting the hidden
   attribute, which the [hidden] rule near the top of the port already handles.
   The platform's own tabs are <button> children of .tabs, so these pages use
   buttons too rather than the spans the grayscale surfaces use. */
.tabpanel {
  display: grid;
  gap: 18px;
}

/* The email preview's own column. The platform's .mailframe is an iframe that
   renders the sender's template, so the centered white message comes from
   inside it; a static page has no iframe, and this is that column drawn
   directly on the frame's ground. */
.mailcard {
  max-width: 560px;
  margin: 0 auto;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 22px;
}

/* A line chart. The approvals platform has none, so this is a wireframe
   addition rather than part of the port: the same primitive the grayscale
   surfaces use, drawn on the ported tokens. */
.linechart {
  height: 180px;
  position: relative;
  border-left: 1px solid var(--line-strong);
  border-bottom: 1px solid var(--line-strong);
}
.linechart svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.chart-axis {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--muted);
  margin-top: 6px;
}

/* ---------- Date range ----------

   The segmented control the main app uses on stag.geodesix.com/admin, beside
   a section heading: the fixed windows as links, then Select date range, which
   opens a panel holding a start, an end and a month. The active window is the
   one pink fill on the row.

   Each option carries its long label and its short one, and the breakpoint
   swaps which is shown: below 1100px the row reads 30d, 90d, Select date range,
   because a type filter and the date range share one row and the sidebar
   takes 232px of the viewport above the drawer breakpoint. */
/* The type filter beside a date range wears the same segmented control: two
   of them on one row, the filter at the left and the window at the right. */
.segbar,
.daterange {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  padding: 3px;
  flex: none;
}
.seg,
.dr-opt {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  padding: 6px 14px;
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
}
.seg:hover,
.dr-opt:hover {
  color: var(--ink);
}
.seg.active,
.dr-opt.active {
  background: var(--pink-dark);
  color: var(--on-dark);
}
.dr-opt .short {
  display: none;
}
.dr-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 20;
  width: 300px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 10px 30px rgba(26, 26, 46, 0.12);
  padding: 16px;
  text-align: left;
}
.dr-cal {
  margin-top: 14px;
}
.dr-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 700;
  font-size: 13.5px;
  margin-bottom: 8px;
}
.dr-head span {
  cursor: pointer;
  color: var(--muted);
  padding: 0 6px;
}
.dr-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  text-align: center;
}
.dr-dow {
  font-size: 11px;
  color: var(--faint);
  font-weight: 600;
  padding: 4px 0;
}
.dr-day {
  font-size: 12.5px;
  padding: 6px 0;
  border-radius: 6px;
  cursor: pointer;
}
.dr-day:hover {
  background: var(--soft-2);
}
.dr-day.in {
  background: var(--pink-soft);
  color: var(--pink-dark);
}
.dr-day.edge {
  background: var(--pink-dark);
  color: var(--on-dark);
}
.dr-day.out {
  color: var(--line-strong);
}

@media (max-width: 1100px) {
  .dr-opt .long {
    display: none;
  }
  .dr-opt .short {
    display: inline;
  }
  .dr-opt.dr-wide {
    display: none;
  }
}

/* Code and placeholders. The approvals platform shows no code and no product
   imagery, so these are wireframe additions rather than part of the port. A
   retailer's surface hands over a snippet, a tracked link and a loader tag,
   and its catalog pages stand in for product photography. */
.codebox {
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 14px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-all;
  color: var(--ink-2);
  overflow-x: auto;
}
.codebox.prose {
  font-family: inherit;
  font-size: 13px;
  word-break: normal;
}
.imgph {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border: 1px dashed var(--line-strong);
  border-radius: 8px;
  background: var(--soft);
  color: var(--faint);
  font-size: 10px;
}

/* A list's foot: the range shown and the way to the next page. A table
   bleeds to the card's edge with a negative bottom margin (table.data or
   .tablewrap, both pulled up 18px so the last row sits flush with the
   card's own border), which is right when the table is the card's last
   child but pulls a following pagination row up into that same edge,
   crossing over the table's bottom line. Give that space back here
   instead of undoing the bleed (Evan, 2026-09-21). */
.card table.data + .pagination,
.card .tablewrap + .pagination {
  margin-top: 18px;
}
.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 2px 0;
  font-size: 12.5px;
  color: var(--muted);
}
.pagination .pages {
  display: flex;
  gap: 8px;
}

/* The ladder a placement climbs, and the one a grounding key climbs: the rungs
   in order with the one it has reached filled. */
.ladder {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.ladder .arrow {
  color: var(--faint);
  font-size: 11px;
}
.ladder-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: 6px;
}

/* The product page stand-in on Install styles, and the five snippet styles it
   shows inside it. These are a wireframe addition and deliberately are not
   built from the tokens above: they are a retailer's own page, drawn in a
   retailer's own neutral palette, so the sample CSS beside each one is the
   same CSS a retailer would paste. */
.pdp {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  padding: 16px;
  max-width: 340px;
}
.pdp .shot {
  height: 110px;
  border: 1px dashed var(--line-strong);
  border-radius: 8px;
  background: var(--soft);
  color: var(--faint);
  display: grid;
  place-items: center;
  font-size: 11px;
  margin-bottom: 12px;
}
.pdp .title {
  font-weight: 700;
  font-size: 14px;
}
.pdp .price {
  font-size: 15px;
  margin: 2px 0 8px;
}
.pdp .desc {
  font-size: 12.5px;
  color: var(--muted);
  margin: 0 0 12px;
}
.pdp .buy {
  display: block;
  text-align: center;
  background: #2b2b2b;
  color: #fff;
  border-radius: 6px;
  padding: 8px 0;
  font-size: 12.5px;
  font-weight: 600;
  margin-top: 12px;
}
.gdx-plain {
  font-size: 13px;
  line-height: 1.6;
}
.gdx-plain .quote {
  font-style: italic;
}
.gdx-plain .source {
  color: #6b6b6b;
}
.gdx-card {
  border: 1px solid #c9c9c9;
  border-radius: 8px;
  padding: 12px 14px;
  background: #f4f4f4;
}
.gdx-card .quote {
  display: block;
  font-size: 14px;
  line-height: 1.5;
  margin-bottom: 8px;
}
.gdx-card .source {
  font-size: 11px;
  color: #6b6b6b;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  font-weight: 600;
}
.gdx-rule {
  border-top: 1px solid #c9c9c9;
  border-bottom: 1px solid #c9c9c9;
  padding: 10px 0;
  display: flex;
  gap: 10px;
  align-items: baseline;
}
.gdx-rule .quote {
  font-size: 13px;
  line-height: 1.5;
  flex: 1;
}
.gdx-rule .source {
  font-size: 11.5px;
  color: #6b6b6b;
  white-space: nowrap;
}
.gdx-pull {
  border-left: 3px solid #2b2b2b;
  padding-left: 14px;
}
.gdx-pull .quote {
  display: block;
  font-size: 17px;
  line-height: 1.45;
  margin-bottom: 6px;
}
.gdx-pull .source {
  font-size: 12px;
  color: #6b6b6b;
}
.gdx-inline {
  font-size: 12.5px;
  line-height: 1.6;
  color: #6b6b6b;
}
.gdx-inline .quote {
  color: #2b2b2b;
}
.gdx-inline .source {
  font-size: 11px;
  border: 1px solid #c9c9c9;
  border-radius: 10px;
  padding: 1px 7px;
  white-space: nowrap;
}

/* The bot check on the login card. A static placeholder: the real one is a
   Cloudflare Turnstile widget, which a file:// page cannot load. */
.turnstile {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--soft);
  padding: 10px 12px;
  font-size: 12px;
  color: var(--muted);
}
.turnstile .tsbox {
  flex: none;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--control-border);
  border-radius: 4px;
  background: var(--paper);
}

/* The account chip menu, and the account switcher inside it.

   A person can be given access to several publishers or several retailers
   through an organization, so the chip is where they move between them. The
   menu is the platform's own panel with three parts added: the signed in
   address, a list of the accounts reachable from this login, and All accounts,
   which is a rollup rather than a place.

   The rollup is drawn rather than described: nav.js sets data-account="all" on
   <body>, which reveals the Account column every list keeps hidden while one
   account is selected, and swaps any figure carrying a data-all value. */
.chipmenu-who {
  display: block;
  padding: 9px 12px 8px;
  font-size: 12px;
  color: var(--faint);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.chipmenu-head {
  margin: 0;
  padding: 10px 12px 4px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--faint);
}
.chipmenu .acct {
  display: flex;
  align-items: center;
  gap: 9px;
}
.chipmenu .acct .logo {
  width: 22px;
  height: 22px;
  font-size: 9px;
  flex: none;
}
.chipmenu .acct .tick {
  margin-left: auto;
  color: var(--pink-primary);
  font-weight: 700;
}
.chipmenu .acct.current {
  font-weight: 600;
}
.chipmenu .acct.all {
  border-top: 1px solid var(--line);
}
.chipmenu .acct.all .logo {
  background: var(--soft-2);
  color: var(--faint);
  letter-spacing: -0.2px;
}

/* An account column is dead weight while one account is selected, so every
   list carries it hidden and the rollup is what reveals it. */
.acct-col {
  display: none;
}
body[data-account="all"] .acct-col {
  display: table-cell;
}

/* A rollup adds rows as well as a column: the other accounts' work is not on
   the page while one account is selected, so those rows are carried hidden the
   same way the Account column is. */
tr.acct-row {
  display: none;
}
body[data-account="all"] tr.acct-row {
  display: table-row;
}

/* An account's mark.

   Admin uploads a 50 by 50 logo for each publisher and retailer and that is what
   the directory lists and the account switcher show. A wireframe has no uploads,
   so it stands one in from Google's favicon service and keeps the initials
   underneath: a real domain resolves to the publication's own mark, a domain
   that does not exist resolves to a generic globe, which is what an account with
   no logo uploaded looks like, and a page opened from file:// with no network
   falls back to the initials.

   This is the one remote asset in the repo. Every other rule holds: no CDN
   scripts, no remote stylesheets, no web fonts. The page renders correctly with
   the image missing, which is why the exception is affordable. */
.logo {
  position: relative;
  overflow: hidden;
}
.logo img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--paper);
  border-radius: inherit;
  /* Transparent until it has actually loaded, so a pending or blocked request
     shows the initials underneath rather than a white square. The onload in the
     markup is what reveals it. */
  opacity: 0;
}

/* The figure a chart ends on, shown once beside the title rather than as a label
   on every point. */
.chart-latest {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
}
.chart-latest .unit {
  font-size: 11px;
  font-weight: 500;
  color: var(--faint);
}

/* A chart you can read by pointing (V1 batch 17). The readout sits where
   .chart-latest did: one figure per line, each with its key, and the window
   under them, which becomes the date pointed at. The dot is HTML over the SVG
   because the SVG stretches to the card and would draw a circle as an oval. */
.chart-readout {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 2px 20px;
  text-align: right;
}
.chart-readout .chart-when {
  flex-basis: 100%;
  font-size: 11px;
  font-weight: 500;
  color: var(--faint);
}
.chart-latest .val {
  font-variant-numeric: tabular-nums;
}
.chart-key {
  display: inline-block;
  width: 12px;
  height: 3px;
  border-radius: 2px;
  background: var(--pink-dark);
  vertical-align: middle;
  margin-right: 5px;
}
.chart-key.s2 {
  background: var(--pink-light);
}
.linechart.live {
  cursor: crosshair;
  touch-action: pan-y;
}
.linechart.live:focus-visible {
  outline: 2px solid var(--pink-dark);
  outline-offset: 4px;
}
.linechart polyline.s2 {
  stroke: var(--pink-light);
  stroke-dasharray: 3 2;
}
.chart-guide {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--line-strong);
  pointer-events: none;
}
.chart-dot {
  position: absolute;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  background: var(--pink-dark);
  box-shadow: 0 0 0 2px #fff;
  pointer-events: none;
}
.chart-dot.s2 {
  background: var(--pink-light);
}
.chart-guide[hidden],
.chart-dot[hidden] {
  display: none;
}
.linechart.live polyline {
  vector-effect: non-scaling-stroke;
  stroke-width: 2px;
}

/* The stacked area chart (2026-09-25; bars until Evan: "The spend should
   be a stacked area chart"): one layer per series, bottom up, in three pinks
   from deep to light (Evan: "change the colors to be versions of pink, so it
   more closely matches our style"; blue and green were drawn first). Direct
   is the deepest, Paid ads the brand pink, Grounding the lightest, so the
   layers step in lightness and read apart without hue. A 1px surface line
   separates layers, and each tile above the chart carries its key. */
:root {
  --viz-1: #8c1156;
  --viz-2: #e0418f;
  --viz-3: #f8b4d7;
}
.linechart.stack polygon {
  stroke: var(--paper);
  stroke-width: 1px;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.linechart.stack polygon.s1 { fill: var(--viz-1); }
.linechart.stack polygon.s2 { fill: var(--viz-2); }
.linechart.stack polygon.s3 { fill: var(--viz-3); }
.chart-dot.v1 { background: var(--viz-1); }
.chart-dot.v2 { background: var(--viz-2); }
.chart-dot.v3 { background: var(--viz-3); }
.chart-key.c1 { background: var(--viz-1); }
.chart-key.c2 { background: var(--viz-2); }
.chart-key.c3 { background: var(--viz-3); }
/* A dashboard tile that stands for the chosen option is marked. */
a.stat.active,
a.stat.active:hover {
  border-color: var(--pink-dark);
  box-shadow: inset 0 0 0 1px var(--pink-dark);
  background: var(--paper);
}

/* Retailer addition: the carousel preview on carousel-detail.html, drawn
   on the .pdp mock beside the snippet styles above it, and the draft state
   that ?new=1 puts the same page into (Evan, 2026-09-22). */
.gdx-carousel .quote,
.gdx-carousel .source {
  display: block;
}
.gdx-carousel .gdx-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 10px;
}
.gdx-carousel .gdx-arrow {
  font-size: 16px;
  line-height: 1;
  color: #6b6b6b;
}
.gdx-carousel .gdx-dots {
  display: flex;
  gap: 6px;
}
.gdx-carousel .gdx-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #c9c9c9;
}
.gdx-carousel .gdx-dots .on {
  background: #2b2b2b;
}
body[data-draft] .if-saved {
  display: none !important;
}

/* Password requirements under a New password field: an open circle per rule
   that becomes a green check once the typed password meets it (V1 batch 12;
   the checking lives in nav.js). */
ul.pwreq {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: grid;
  gap: 3px;
  font-size: 12.5px;
  color: var(--muted);
}
ul.pwreq li {
  display: flex;
  align-items: center;
  gap: 8px;
}
ul.pwreq li::before {
  content: '';
  flex: none;
  width: 12px;
  height: 12px;
  box-sizing: border-box;
  border: 1.5px solid var(--control-border);
  border-radius: 50%;
}
ul.pwreq li.met {
  color: var(--ok);
}
ul.pwreq li.met::before {
  content: '\2713';
  border: 0;
  font-size: 12px;
  line-height: 12px;
  text-align: center;
  font-weight: 700;
}

/* ---------- Older wireframe controls, as on admin ----------

   The same block v1-admin carries, for the seven retailer pages first drawn
   on the grayscale retailer (request-detail, select-citation, invite-user,
   verify-installation, and three superseded pages). V1 batch 12 moved them
   onto the port's frame, stat row, tables, fields and tabs; these are the
   controls left with no port component. */
q {
  quotes: none;
}
.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.page-head .ph-text {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}
.page-head .ph-text:empty {
  display: none;
}
.page-head > div:last-child:not(.ph-text) {
  display: flex;
  gap: 8px;
  margin-left: auto;
}
.grid {
  display: grid;
  gap: 16px;
}
.grid.cols-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.grid.cols-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.grid.cols-4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 1040px) {
  .grid.cols-4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 900px) {
  .grid.cols-3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (max-width: 720px) {
  .grid.cols-2,
  .grid.cols-3 {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 520px) {
  .grid.cols-4 {
    grid-template-columns: 1fr;
  }
}
.label {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--ink-2);
  margin-bottom: 5px;
}
.grid > .field {
  margin-bottom: 0;
}
table.data tr.clickable {
  cursor: pointer;
}
table.data tr.clickable:hover td {
  background: var(--soft);
}
.btn.disabled {
  opacity: 0.45;
  pointer-events: none;
}
.typeahead {
  display: block;
  position: relative;
}
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 11px;
  border: 1px solid var(--control-border);
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  background: var(--paper);
  cursor: pointer;
}
.chip.active {
  border-color: var(--pink-primary);
  color: var(--pink-dark);
  background: var(--pink-soft, #fdf0f7);
}
.selectcard {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--control-border);
  border-radius: 10px;
  background: var(--paper);
  cursor: pointer;
  font-size: 13.5px;
}
.selectcard.sel {
  border-color: var(--pink-primary);
  box-shadow: 0 0 0 1px var(--pink-primary) inset;
}
.selectcard .ic {
  flex: none;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  border: 1.5px solid var(--control-border);
  border-radius: 50%;
}
.selectcard.sel .ic {
  border: 5px solid var(--pink-primary);
}
.switch {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  cursor: pointer;
}
.switch .track,
.switchrow .sw {
  flex: none;
  position: relative;
  width: 32px;
  height: 18px;
  border-radius: 999px;
  background: var(--line-strong);
}
.switch .track::after,
.switchrow .sw::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--paper);
}
.switch.on .track,
.switchrow.on .sw {
  background: var(--pink-primary);
}
.switch.on .track::after,
.switchrow.on .sw::after {
  left: 16px;
}
.switchrow {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
  cursor: pointer;
}
.switchrow:first-child {
  border-top: 0;
}
.switchrow .swtext b,
.switchrow .swtext span {
  display: block;
}
.switchrow .swtext span {
  color: var(--muted);
  font-size: 12.5px;
}
.publogo {
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 6px;
  background: var(--soft-2);
  color: var(--muted);
  font-size: 9px;
  font-weight: 700;
  vertical-align: middle;
}
.bodytext {
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-2);
}
.bodytext .hl,
.hl {
  background: var(--pink-soft, #fdf0f7);
  border-bottom: 2px solid var(--pink-primary);
}
.ph {
  display: grid;
  place-items: center;
  border: 1px dashed var(--line-strong);
  border-radius: 8px;
  background: var(--soft);
  color: var(--faint);
  font-size: 12px;
}
.listrow {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
  font-size: 13.5px;
}
.listrow:first-child {
  border-top: 0;
}
.listrow .meta {
  color: var(--muted);
  font-size: 12.5px;
  white-space: nowrap;
}

.selectcard b {
  display: block;
  margin-bottom: 2px;
}

/* The back link and the page's own action (View as, Edit, a status) on one
   row, the link at the left and the action at the right (V1 batch 13). */
.backrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.backrow .backlink {
  margin: 0;
}
.backrow-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* A select is as tall as a text field beside it. The native control draws
   its box a few pixels shorter from the same padding, so the height is set
   rather than left to the padding (V1 batch 13). Filter bar and table
   selects keep their smaller padding and height. */
select {
  box-sizing: border-box;
  min-height: calc(1.55em + 20px);
}
.filterbar select,
table.data select {
  min-height: 0;
}

/* A tab bar wider than its column scrolls sideways instead of wrapping,
   so a detail page with many tabs keeps them on one row (V1 batch 13). */
.tabs {
  overflow-x: auto;
  scrollbar-width: thin;
}
.tabs button {
  flex: none;
  white-space: nowrap;
}
.tabs {
  gap: 0;
}
.tabs button {
  padding: 8px 10px;
}

/* An input whose unit is joined to its right edge (V1 batch 16): the unit is
   part of the control, drawn like it, on a tinted ground so it reads as fixed
   rather than typed. Used for the grounding rate ceiling. */
.inputaffix {
  display: flex;
  align-items: stretch;
  max-width: 260px;
}
.inputaffix input {
  flex: 1;
  min-width: 0;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.inputaffix .affix {
  display: flex;
  align-items: center;
  padding: 0 12px;
  border: 1px solid var(--control-border);
  border-left: 0;
  border-radius: 0 8px 8px 0;
  background: var(--soft-2);
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

/* The weekly email on Your profile (V1 batch 16, Evan: "a much clearer
   breakdown ... then it should list out the first 3 citations, products,
   publishers"): one row per implementation type with what it counted and
   what it cost, a total, then short lists with product images and logos. */
.mailsum {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 18px;
  font-size: 13.5px;
}
.mailsum td {
  padding: 8px 0;
  border-top: 1px solid var(--line);
  vertical-align: top;
}
.mailsum td + td {
  padding-left: 12px;
}
.mailsum td .muted {
  display: block;
  font-size: 12px;
}
.mailsum td.num {
  text-align: right;
  white-space: nowrap;
}
.mailsum tr.total td {
  border-top: 2px solid var(--ink);
  font-weight: 700;
}
.maillist {
  margin: 6px 0 18px;
}
.maillist:last-child {
  margin-bottom: 0;
}
.mailrow {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.mailrow:first-child {
  border-top: 0;
}
.mailrow > .imgph,
.mailrow > .logo {
  flex: none;
  width: 48px;
  height: 48px;
}
.mailrow .body {
  flex: 1;
  min-width: 0;
  font-size: 13.5px;
}
.mailrow .body > b {
  display: block;
}
.mailrow .q {
  margin: 2px 0 4px;
  color: var(--ink-2);
}
.mailrow .by {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
}

/* A part of a card that used to be a card of its own: the Get started tabs
   hold a type's install pieces and its verify checks one under another
   (V1 batch 16), each a rule and a small heading rather than a nested card. */
.subpart {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}
.subpart > .row-between h4,
.tabpanel > .row-between h4 {
  font-size: 15px;
}
/* The first subpart under a tab's own heading needs no rule above it (Evan,
   2026-09-24: "Too much white space above and below this"). */
.subpart.first {
  margin-top: 6px;
  padding-top: 0;
  border-top: 0;
}
/* A section of an email preview sits in its own box rather than floating
   (Evan, 2026-09-24: "Put each section in boxes so that they aren't just
   floating"): the weekly summary, then each list with its heading. */
.mailbox {
  margin-top: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 8px;
}
.mailbox > .mailsum {
  margin: 0;
}
.mailbox > .mailhead {
  margin-bottom: 4px;
}
/* The weekly email's usage and cost: a row per implementation type, three
   tiles in each (a figure, its label, and the change from last week, up in
   green and down in red), and the total under them (Evan, 2026-09-24).
   "vs." is said once, in the box's heading, rather than under every tile. */
.mailtype {
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.mailhead + .mailtype {
  border-top: 0;
  padding-top: 6px;
}
.mailtype .name {
  margin: 0 0 6px;
  font-size: 13.5px;
  font-weight: 700;
}
.mailtiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.mailtile {
  min-width: 0;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--soft);
}
.mailtile .label {
  display: block;
  font-size: 11px;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mailtile b {
  display: block;
  font-size: 17px;
  line-height: 1.3;
  white-space: nowrap;
}
.mailtile .delta,
.mailtotal .delta {
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}
.delta.up {
  color: var(--ok);
}
.delta.down {
  color: var(--bad);
}
.delta.flat {
  color: var(--muted);
  font-weight: 500;
}
.mailtotal {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding-top: 10px;
  border-top: 2px solid var(--ink);
  font-size: 13.5px;
}
.mailtotal b {
  margin-left: auto;
}
/* The grounding alert: queries by day as a month of squares, the darker the
   busier (Evan, 2026-09-24: "squares that give some indication of usage,
   similar to how GitHub works"), and spend as a bar against the alert and
   the cap. */
.heatmap {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
  margin-top: 8px;
}
/* The calendar takes the full width of the email (Evan, 2026-09-24: "This
   should take up the full width of the email"): seven equal columns, each
   day a square of whatever width its column gets. */
.heatmap .day {
  width: auto;
  height: auto;
  aspect-ratio: 1;
  border-radius: 6px;
}
/* Each day carries its date at the top left, the way a calendar does (V1
   batch 17). White on the darkest pink, ink on the rest, which is the pair
   that keeps each above 4.5:1. */
.heatmap .day {
  padding: 4px 0 0 5px;
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.heatmap .day.l4 {
  color: #fff;
}
.heatmap .day.future {
  color: var(--faint);
  font-weight: 500;
}
.heatmap .dow {
  font-size: 11px;
  color: var(--muted);
  text-align: center;
}
.day {
  display: inline-block;
  width: 26px;
  height: 26px;
  border-radius: 5px;
  background: var(--soft-2);
}
.day.blank {
  visibility: hidden;
}
.day.future {
  background: transparent;
  border: 1px dashed var(--line-strong);
}
.day.l1 {
  background: #fce7f3;
}
.day.l2 {
  background: #f9a8d4;
}
.day.l3 {
  background: var(--pink-light);
}
.day.l4 {
  background: var(--pink-dark);
}
.heatkey {
  display: flex;
  align-items: center;
  gap: 3px;
  margin-top: 8px;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}
.heatkey .day {
  width: 11px;
  height: 11px;
  border-radius: 3px;
}
.heatkey .day:first-child {
  margin-left: 4px;
}
.heatkey .day:last-child {
  margin-right: 4px;
}
.usebar {
  position: relative;
  height: 12px;
  margin: 12px 0 10px;
  border-radius: 999px;
  background: var(--soft-2);
}
.usebar .fill {
  position: absolute;
  inset: 0 auto 0 0;
  border-radius: 999px;
  background: var(--pink-primary);
}
.usebar .mark {
  position: absolute;
  top: -4px;
  bottom: -4px;
  width: 2px;
  margin-left: -1px;
  background: var(--ink);
}
.usekey {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  font-size: 12px;
  color: var(--ink-2);
}
.usekey span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.usekey .k {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 3px;
}
.usekey .k.fill {
  background: var(--pink-primary);
}
.usekey .k.mark {
  width: 2px;
  height: 14px;
  border-radius: 0;
  background: var(--ink);
}
.usekey .k.cap {
  background: var(--soft-2);
  border: 1px solid var(--line-strong);
}
@media (max-width: 720px) {
  .mailframe {
    padding: 12px 10px;
  }
  .mailcard {
    padding: 16px 14px;
  }
  .mailbox {
    padding: 10px;
  }
  .mailtiles {
    gap: 6px;
  }
  .mailtile {
    padding: 6px;
  }
  .mailtile b {
    font-size: 16px;
  }
}
/* Get started's tabs sit outside the cards, as on Grounding (Evan,
   2026-09-24). The wrapper only scopes the tabs for nav.js; its children
   are laid out as the page's own. */
.gs-steps {
  display: contents;
}
.mailsum td.num:not(:last-child) {
  white-space: normal;
}
.mailhead {
  flex-wrap: nowrap;
  gap: 10px;
  margin-bottom: 0;
}
.mailhead .cite-heading {
  margin: 0;
}

/* The summary email's settings (profile.html, Notifications; Evan,
   2026-09-24: "they should be able to indicate what is included in the
   email and whether they want one email for all their accounts or
   separate emails"), modeled on the live app's Email digest. Delivery
   picks one email for every account or one per account; one email has a
   single How often, Accounts and What it covers, one per account has a
   card for each account holding its own. The preview beside it follows
   the choices with no script: the rules under this comment read the
   inputs through :has(), so the account blocks, the totals, the name
   under the title and each section of the summary show or hide as the
   boxes change. The preview in per account mode is Ulta Beauty's email. */
.cardstack {
  display: flex;
  flex-direction: column;
  gap: 18px;
  min-width: 0;
}
.digest {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.pickitem .logo {
  width: 22px;
  height: 22px;
  font-size: 9px;
  flex: none;
}
details.acctmail {
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
details.acctmail > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 8px 14px;
  cursor: pointer;
  list-style: none;
}
details.acctmail > summary::-webkit-details-marker {
  display: none;
}
details.acctmail > summary::after {
  content: '';
  flex: none;
  margin-left: auto;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg);
  transition: transform 0.2s;
}
details.acctmail[open] > summary::after {
  transform: rotate(-135deg);
}
details.acctmail > summary .logo {
  width: 32px;
  height: 32px;
  font-size: 12px;
  flex: none;
}
details.acctmail .acctname {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.35;
}
.acctmail-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 0 14px 14px;
}
.mailacct {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 0;
  font-size: 15px;
  font-weight: 700;
}
.mailacct .logo {
  width: 22px;
  height: 22px;
  font-size: 9px;
  flex: none;
}
.mailacct + .mailtype {
  border-top: 0;
  padding-top: 6px;
}
.mailacctblock + .mailacctblock {
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid var(--line);
}
.notif:has(#d-one:checked) .if-each,
.notif:has(#d-each:checked) .if-one,
.notif:has(#d-each:checked) [data-acct="target"],
.notif:has(#d-one:checked):has(#a1-ulta:not(:checked)) [data-acct="ulta"],
.notif:has(#d-one:checked):has(#a1-target:not(:checked)) [data-acct="target"],
.notif:has(#d-each:checked) [data-for="all"],
.notif:has(#d-each:checked) [data-for="target"],
.notif:has(#d-one:checked):has(#a1-ulta:not(:checked)) [data-for="all"],
.notif:has(#d-one:checked):has(#a1-target:not(:checked)) [data-for="all"],
.notif:has(#d-one:checked):has(#a1-target:checked) [data-for="ulta"],
.notif:has(#d-one:checked):has(#a1-ulta:not(:checked)) [data-for="ulta"],
.notif:has(#d-one:checked):has(#a1-ulta:checked) [data-for="target"],
.notif:has(#d-one:checked):has(#a1-target:not(:checked)) [data-for="target"],
.notif:has(#d-one:checked):has(#c1-usage:not(:checked)) [data-covers="usage"],
.notif:has(#d-each:checked):has(#cu-usage:not(:checked)) [data-covers="usage"],
.notif:has(#d-one:checked):has(#c1-cites:not(:checked)) [data-covers="cites"],
.notif:has(#d-each:checked):has(#cu-cites:not(:checked)) [data-covers="cites"],
.notif:has(#d-one:checked):has(#c1-products:not(:checked)) [data-covers="products"],
.notif:has(#d-each:checked):has(#cu-products:not(:checked)) [data-covers="products"],
.notif:has(#d-one:checked):has(#c1-pubs:not(:checked)) [data-covers="pubs"],
.notif:has(#d-each:checked):has(#cu-pubs:not(:checked)) [data-covers="pubs"] {
  display: none;
}
.notif:has(#d-one:checked):has(#a1-ulta:not(:checked)) .mailacctblock + .mailacctblock {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* IP filtering on the profile (2026-09-25): a list of single addresses whose
   traffic is not counted or billed, and a form to add one. .inlinerow puts
   the address and Use my IP address side by side at the input's height, as
   on admin; hr.rule is admin's divider inside a card. */
hr.rule {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 24px -20px 18px;
}
.inlinerow {
  display: flex;
  gap: 10px;
  align-items: stretch;
  flex-wrap: wrap;
}
.inlinerow input,
.inlinerow select {
  flex: 1 1 180px;
  min-width: 0;
  width: auto;
}
.inlinerow .btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  min-height: 0;
}
/* An address, its note and its date stay on one line each; on a phone the
   table scrolls inside its .tablewrap rather than breaking an address. */
[data-ip] table.data td {
  white-space: nowrap;
}

/* ------------------------------------------------------------------------
   V0 additions. This stylesheet is v1-retailer/web/wireframe/style.css in
   whole (the port plus the retailer's additions), and the same file serves
   all three V0 surfaces, so nothing below is surface specific. What follows
   is the handful of rules the V1 admin and publisher stylesheets carried
   that the retailer's did not, copied verbatim so a page ported from either
   surface still renders: .inlineform (admin's one row settings), .mail-ent
   (the email preview's logo column) and .scopelist (a checkbox list under
   an Only these choice).
   ------------------------------------------------------------------------ */
/* A setting that is one field and one button, side by side on one row
   (V1 batch 17): the organization's name and Save, the delete confirmation
   and Delete. The input and the button share .inlinerow, which stretches
   the button to the input's height at every size (V1 batch 18, Evan: "Make
   the save button the same height as the input box"). */
.inlineform {
  max-width: 620px;
  margin-bottom: 0;
}
.mail-ent {
  display: grid;
  grid-template-columns: var(--mail-logo, 56px) minmax(0, 1fr);
  gap: 14px;
  align-items: stretch;
}
.mail-ent > div {
  min-width: 0;
}
/* The new request's logo is the larger one, 72px (96px until V1 batch 17,
   Evan: "Make the image a little less tall"). */
.mail-ent.lg {
  --mail-logo: 72px;
}
.mail-ent > .logo {
  width: 100%;
  height: auto;
  min-height: var(--mail-logo, 44px);
  border-radius: 10px;
  font-size: 13px;
}
.mail-ent > .logo img {
  object-fit: contain;
  padding: 18%;
  box-sizing: border-box;
}
.mail-ent > div > .url-line,
.mail-ent > div > .cite-when {
  display: block;
  white-space: normal;
  overflow: visible;
}
.scopelist {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 8px 0 0 26px;
}
.scopelist input[type=search] {
  width: 100%;
}
.pickgroup:has(input[value="all"]:checked) .scopelist {
  display: none;
}
.mailempty {
  margin: 6px 0 0;
  font-size: 13px;
  color: var(--muted);
}

/* The one month calendar on a page (V0): the email's .heatmap squares drawn
   inside a card, full width. A card is far wider than an email, so a day is
   a short wide cell rather than a square, the shape a calendar month has,
   and it never drops under 40px tall, which is what keeps it a target on a
   phone. The figures are read by pointing, as on a chart. */
.heatmap[data-calendar] {
  gap: 8px;
  margin-top: 12px;
}
.heatmap[data-calendar] .day {
  aspect-ratio: 2.4 / 1;
  min-height: 40px;
  font-size: 12px;
  padding: 6px 0 0 8px;
  cursor: default;
}
.heatmap[data-calendar] .day:focus-visible {
  outline: 2px solid var(--pink-primary);
  outline-offset: 2px;
}
.heatmap[data-calendar] .day.blank {
  visibility: hidden;
}
@media (max-width: 720px) {
  .heatmap[data-calendar] { gap: 5px; }
  .heatmap[data-calendar] .day { aspect-ratio: 1; padding: 4px 0 0 5px; font-size: 10.5px; }
}

/* The summary email preview follows What it covers (V0). V1 keyed these on
   the Delivery radio, which V0 dropped, so the sections hide on the boxes
   alone; the retailer's and the publisher's box ids are both listed since
   the stylesheet is shared. */
.notif:has(#c1-usage:not(:checked)) [data-covers="usage"],
.notif:has(#c1-cites:not(:checked)) [data-covers="cites"],
.notif:has(#c1-products:not(:checked)) [data-covers="products"],
.notif:has(#c1-pubs:not(:checked)) [data-covers="pubs"],
.notif:has(#c1-placements:not(:checked)) [data-covers="placements"],
.notif:has(#c1-retailers:not(:checked)) [data-covers="retailers"] {
  display: none;
}
