/* Truewares theme.
 *
 * The palette, the mark geometry, and the typeface are the brand record in the
 * marketing repo (website/logo/BRAND.md). Do not invent new brand colors here.
 * Anything in this file that departs from that record says why it does.
 */

/* Every themed value is one light-dark() pair rather than two blocks of tokens
   kept in sync by hand. The forgotten half of a duplicated palette is the usual
   way a dark mode ends up with one unreadable widget in it.

   color-scheme carries the whole theme: left at "light dark" the pairs resolve
   against the OS preference, and the toggle in the header pins it by setting
   data-theme, which flips color-scheme and every pair with it. Native widgets,
   scrollbars, and form controls follow the same property for free.

   The cost is a hard browser floor, because there is no fallback: a browser
   without light-dark() throws out every declaration below and renders black on
   white with black borders. Chrome 123, Safari 17.5, and Firefox 120, so
   roughly mid-2024 and, for Safari, macOS 12 or later. If that floor ever has
   to come down, the fix is two literal palettes, one here and one under
   @media (prefers-color-scheme: dark), not a partial hedge. */
:root {
  color-scheme: light dark;

  --bg:        light-dark(#f8fafc, #0b1220);
  --panel:     light-dark(#ffffff, #0f1a2c);
  --sunken:    light-dark(#f1f5f9, #142235);
  --field:     light-dark(#ffffff, #0a1526);
  --ink:       light-dark(#0f172a, #f1f5f9);
  /* Light is a step darker than the obvious #64748b, and the reason is the
     background it sits on. #64748b measures 4.76:1 on white and passes, but
     muted text is not only on white: on --sunken (#f1f5f9) it is 4.34:1 and on
     a highlighted row (#f2f9fc) 4.47:1, both under the 4.5:1 floor for text
     this size. #475569 is 6.9:1 on --sunken, so the token clears AA wherever it
     lands rather than only on the page background. Dark needs no such step;
     #94a3b8 is 7.0:1 on --sunken there. */
  --muted:     light-dark(#475569, #94a3b8);
  --line:      light-dark(#e2e8f0, #22304a);

  /* Sky, split in two. #0284c7 is the brand dot and it measures 4.09:1 on
     white, which clears the 3:1 floor for a graphic but not the 4.5:1 floor for
     text. So the dot color stays on the mark and on fills, and links and labels
     use a darker step of the same hue. Dark mode needs no split: #7dd3fc is
     11.2:1 on the page. */
  --brand-bar:   light-dark(#475569, #7c8ba1);
  --brand-dot:   light-dark(#0284c7, #7dd3fc);
  --accent:      light-dark(#0369a1, #7dd3fc);
  --accent-fill: light-dark(#0369a1, #38bdf8);
  --on-accent:   light-dark(#ffffff, #0b1220);
  --accent-soft: light-dark(#e0f2fe, #10273c);

  --danger:      light-dark(#b91c1c, #fca5a5);
  --danger-bg:   light-dark(#fef2f2, #2a1417);
  --danger-line: light-dark(#fecaca, #4c1d1d);

  --ok:      light-dark(#047857, #6ee7b7);
  --ok-bg:   light-dark(#ecfdf5, #0c2a20);
  --ok-line: light-dark(#a7f3d0, #14503b);

  --warn:      light-dark(#92400e, #fcd34d);
  --warn-bg:   light-dark(#fffbeb, #2a2008);
  --warn-line: light-dark(#fde68a, #4d3b12);

  --shadow: light-dark(rgba(15, 23, 42, .07), rgba(0, 0, 0, .45));
  --radius: 10px;
}

/* The two pinned states. Nothing but color-scheme changes, because every token
   above already reads from it. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

* { box-sizing: border-box; }

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

main { max-width: 60rem; margin: 0 auto; padding: 2rem 1.25rem 4rem; }

/* Inside the shell the column is already bounded by the sidebar, so main only
   needs to cap the measure, not centre itself against the viewport. */
.content main { margin: 0; }

/* Breadcrumbs. Above the heading, with enough air that the trail is its own
   line of chrome rather than a subtitle crowding the title.
   The separator is a ::before rather than a character in the markup, so a
   screen reader reads the names and not a row of slashes, and it wraps as a
   unit with the crumb it belongs to. */
.crumbs { margin: 0 0 .85rem; font-size: .85rem; }
.crumbs ol {
  display: flex; flex-wrap: wrap; align-items: baseline;
  margin: 0; padding: 0; list-style: none;
}
.crumbs li { color: var(--muted); overflow-wrap: anywhere; }
/* The separator carries the text colour, not the border colour. Drawn in
   --line it was the weight of a hairline rule and the trail read as one
   run-on phrase in both themes. */
.crumbs li + li::before { content: "/"; margin: 0 .4rem; color: var(--muted); }
.crumbs a { color: var(--muted); text-decoration: none; }
.crumbs a:hover { color: var(--accent); text-decoration: underline; }
/* The page you are on. Named, not linked, and darker and heavier than the path
   to it, matching how the sidebar and the switcher mark their current item. */
.crumbs [aria-current="page"] { color: var(--ink); font-weight: 600; }

h1 { font-size: 1.6rem; margin: 0 0 1.25rem; }
h2 { font-size: 1.05rem; margin: 0 0 .6rem; }
/* Smaller than h2 but with air above it, because an h3 is always a divider
   inside a card that already has a heading. The extra top margin is what keeps
   it from reading as a label for whatever line ended just above. */
h3 { font-size: .95rem; margin: 1.5rem 0 .5rem; }
/* Except at the top of its container, where there is nothing to separate it
   from and the gap becomes an unexplained hole. */
:is(.card, form, section) > h3:first-child { margin-top: 0; }

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

/* --------------------------------------------------------------- identity */

.tw-lockup { display: inline-flex; color: var(--ink); text-decoration: none; }

/* The letters take currentColor from the lockup, so the page controls them and
   the light and dark switch is correct. Only the mark is pinned, and it stays
   duotone: the single-ink file is the starting geometry, not the intended
   finish. */
.tw-wordmark { display: block; width: auto; fill: currentColor; }
.tw-wordmark .tw-bar { fill: var(--brand-bar); }
.tw-wordmark .tw-dot { fill: var(--brand-dot); }
.brand:hover .tw-dot { fill: var(--accent-fill); }

/* Height, not font-size: the wordmark is artwork now, not text. The brand
   record sets two floors, 16px of cap height and 110px of width, and this
   clears both. Cap height is 24 of the 24.38 unit box, so it is very nearly
   the full height, and the box is trimmed to the ink. */
.brand .tw-wordmark { height: 1.2rem; }

/* The wordmark files carry no padding of their own. One bar height of clear
   space on every side is the brand rule, and at this size that is about 4px. */
.brand { display: inline-block; padding: .3rem; }

/* ------------------------------------------------------------- page chrome */

/* Wraps rather than overflows. Signed in, the bar carries the lockup, four
   nav links, the shop chip, an email address, the theme control, and sign out,
   which needs about 740px. On a phone the unwrapped row scrolled the whole
   document sideways. */
.bar {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: .5rem 1rem; padding: .75rem 1.25rem;
  background: var(--panel); border-bottom: 1px solid var(--line);
}
.bar nav { display: flex; flex-wrap: wrap; gap: 1rem; margin-right: auto; margin-left: 1.5rem; }
.bar nav a { color: var(--muted); text-decoration: none; font-size: .92rem; }
.bar nav a:hover { color: var(--ink); }
.bar form { display: inline; }
.bar-right { display: flex; align-items: center; gap: .9rem; }
.who { color: var(--muted); font-size: .85rem; }
.shop { color: var(--muted); font-size: .9rem; }
form.inline { display: inline; margin: 0; }

/* The toggle only works with script, so the markup ships it hidden and the
   script reveals it. A dead control is worse than no control. */
.theme-toggle {
  margin: 0; padding: .3rem; width: 1.9rem; height: 1.9rem;
  display: grid; place-items: center;
  background: transparent; color: var(--muted);
  border: 1px solid var(--line); border-radius: 999px;
}
/* An author `display` beats the user agent's `[hidden] { display: none }`, so
   without this the button renders anyway and a scriptless browser gets the
   dead control the hidden attribute was there to prevent. */
.theme-toggle[hidden] { display: none; }
.theme-toggle:hover { color: var(--accent); border-color: var(--accent); filter: none; }
.theme-toggle svg { width: 1rem; height: 1rem; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
.theme-toggle [data-icon] { display: none; }

/* Which glyph shows is a pure function of the pinned state, so the script sets
   one attribute and does not touch the icons. */
:root:not([data-theme]) .theme-toggle [data-icon="auto"],
:root[data-theme="light"] .theme-toggle [data-icon="light"],
:root[data-theme="dark"]  .theme-toggle [data-icon="dark"] { display: block; }

/* --------------------------------------------------------------- surfaces */

.card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1.25rem; margin-bottom: 1.25rem;
  box-shadow: 0 1px 2px var(--shadow);
}
.card.narrow { max-width: 24rem; margin: 4rem auto; }

/* The not-found page. No card and no border: this is a dead end rather than a
   panel of content, and the page reads better as a quiet notice in the middle
   of the space than as an empty box.

   Centred in the viewport when it is the whole page, and in the column when it
   is inside the shell, which is what min-height on a dvh unit does here: the
   sidebar already caps its own height, so this cannot add a scrollbar to it. */
.notfound {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .35rem; min-height: 60dvh; text-align: center;
}
/* With no header above it the page is only this block, so it takes the whole
   viewport and sits in the middle of it. dvh rather than vh: on a phone the URL
   bar would otherwise push it into a scroll. */
main.fill { display: grid; place-items: center; min-height: 100dvh; }
main.fill .notfound { min-height: 0; }
.notfound-code {
  margin: 0; font-size: clamp(3.5rem, 14vw, 6rem); font-weight: 650; line-height: 1;
  color: var(--line);
  /* Tabular figures so the three digits sit on an even rhythm at display size,
     where proportional ones read as a wobble. */
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
.notfound h1 { margin: .35rem 0 0; font-size: 1.35rem; }
.notfound .muted { margin: 0; }

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 1rem; margin-bottom: 1.25rem; }
.tile {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 1rem 1.25rem;
  display: flex; flex-direction: column; gap: .2rem;
  box-shadow: 0 1px 2px var(--shadow);
}
.figure { font-size: 1.75rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.label { color: var(--muted); font-size: .85rem; }

.meter { height: .6rem; background: var(--sunken); border-radius: 999px; overflow: hidden; margin: .5rem 0; }
.meter-fill { height: 100%; background: var(--brand-dot); border-radius: 999px; }

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

label { display: block; margin: .9rem 0 .3rem; font-size: .9rem; color: var(--muted); }
input, select, textarea {
  font: inherit; color: var(--ink);
  background: var(--field);
  border: 1px solid var(--line); border-radius: 7px;
}
input, textarea { width: 100%; padding: .55rem .7rem; }
select { padding: .4rem .55rem; }

/* A textarea is not covered by the input rules for free: it is its own element,
   and left alone the browser gives it a system background and a width in
   columns, which reads as a disabled field sitting in the wrong place. It also
   defaults to resizing in both directions, and a field a merchant can drag
   wider than its card is a layout that breaks on their screen and not on ours.
   Vertical only, with a floor that fits a couple of sentences. */
textarea {
  display: block;
  resize: vertical;
  min-height: 6rem;
  line-height: 1.5;
}

input:focus, select:focus, textarea:focus, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--brand-dot); outline-offset: 1px;
}

/* The roles page renders a built-in role as a filled but disabled form. Without
   this it looks exactly like an editable one, and the only way to find out
   otherwise is to click into a field and have nothing happen. */
input:disabled, select:disabled, textarea:disabled, fieldset:disabled {
  background: var(--sunken); color: var(--muted); cursor: not-allowed;
}
fieldset:disabled { background: transparent; opacity: .65; }

button {
  margin-top: 1.1rem; padding: .55rem 1rem; font: inherit; font-weight: 550;
  background: var(--accent-fill); color: var(--on-accent);
  border: 0; border-radius: 7px; cursor: pointer;
}
button:hover { filter: brightness(1.08); }
button.link {
  margin: 0; padding: 0; background: none; color: var(--muted);
  font-weight: 400; text-decoration: underline;
}
button.link:hover { color: var(--ink); filter: none; }
button.small { margin: 0; padding: .3rem .65rem; font-size: .85rem; }
button.danger { background: transparent; color: var(--danger); border: 1px solid var(--line); }
button.danger:hover { border-color: var(--danger); filter: none; }

/* .on is a toggle that is currently on, and the admin user list puts one in
   every row. Filled for on, outlined for off: painting both in the accent left
   "Yes, revoke" and "No, grant" looking like the same button, so the state was
   only legible by reading the label. */
button.on { background: var(--accent-fill); color: var(--on-accent); border: 1px solid transparent; }
button.small:not(.on):not(.danger) {
  background: transparent; color: var(--accent); border: 1px solid var(--line);
}
button.small:not(.on):not(.danger):hover { border-color: var(--accent); filter: none; }

/* A second action beside a primary one: outlined, but the same size.
   .small also draws an outline and is the obvious thing to reach for here, but
   it shrinks the control too, so the secondary action ends up both quieter and
   harder to hit. Weight is the right way to rank two actions; target size is
   not, and under a coarse pointer that shrink is the difference between two
   comfortable targets and one comfortable one next to a fiddly one. */
button.secondary {
  background: transparent; color: var(--accent); border: 1px solid var(--line);
}
button.secondary:hover { border-color: var(--accent); filter: none; }

.chip {
  margin: 0; padding: .25rem .6rem; font-size: .85rem; font-weight: 550;
  background: transparent; color: var(--ink);
  border: 1px solid var(--line); border-radius: 999px;
}
.chip:hover { border-color: var(--accent); color: var(--accent); filter: none; }

fieldset {
  margin: 1.1rem 0 0; padding: .9rem 1rem;
  border: 1px solid var(--line); border-radius: 8px;
}
legend { padding: 0 .4rem; color: var(--muted); font-size: .85rem; }

label.check {
  display: flex; gap: .6rem; align-items: flex-start;
  margin: .5rem 0; font-size: .92rem; color: var(--ink);
}
label.check input { width: auto; margin-top: .25rem; flex: none; accent-color: var(--accent-fill); }

/* The honeypot's wrapper. Positioned off screen rather than display:none or
   hidden, because the field has to look real to something filling in every
   input it finds, and both of those are the first things such a script skips.
   aria-hidden and tabindex="-1" in the markup are what keep it away from
   anybody actually using the page. */
.trap {
  position: absolute; left: -9999px;
  width: 1px; height: 1px; overflow: hidden;
}

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

table { width: 100%; border-collapse: collapse; font-size: .92rem; }
th, td { text-align: left; padding: .5rem .6rem; border-bottom: 1px solid var(--line); }
th { color: var(--muted); font-weight: 550; font-size: .8rem; text-transform: uppercase; letter-spacing: .03em; }
/* th too, not only td: a row header table (scope="row", as on the connections
   page) otherwise leaves a stub of rule under the last label cell. */
tbody tr:last-child th, tbody tr:last-child td { border-bottom: 0; }

/* A six column table does not fit a phone. Scroll it inside its own card rather
   than letting it widen the document, which would move the header and every
   other card sideways along with it.

   nowrap is what makes the scroll worth having: without it the columns crush to
   one word per line and the table gets taller instead of scrollable. */
@media (max-width: 46rem) {
  .card > table {
    display: block; width: auto; overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .card > table th, .card > table td { white-space: nowrap; }

  /* Columns worth dropping before the ones that identify the row. Marked in the
     template rather than guessed by position, so reordering a table does not
     silently hide the wrong thing. */
  .card > table .opt { display: none; }

  .bar nav { margin-left: 0; }
}

/* Long, unbroken strings are the usual source of a sideways document: an email
   address in a table cell, a shop name in the switcher. */
td, .who-email, .switcher-name, .switcher-item-name { overflow-wrap: anywhere; }
.card > table td { overflow-wrap: normal; }

/* ---------------------------------------------------------------- feedback */

.muted { color: var(--muted); font-size: .9rem; }

.error {
  background: var(--danger-bg); border: 1px solid var(--danger-line); color: var(--danger);
  padding: .7rem .9rem; border-radius: 7px; margin-bottom: 1rem;
}
.notice {
  background: var(--ok-bg); border: 1px solid var(--ok-line); color: var(--ok);
  padding: .7rem .9rem; border-radius: 7px; margin-bottom: 1rem;
}
.banner {
  margin: 0; padding: .6rem 1.25rem; font-size: .9rem;
  background: var(--warn-bg); color: var(--warn); border-bottom: 1px solid var(--warn-line);
}

/* One product in the sample store's control panel. A rule so that a dozen
   items with a table and a row of buttons each read as a dozen blocks rather
   than as one wall, which is what they were before the separator. */
.sample-item { padding-block: 1rem; border-top: 1px solid var(--line); }
.sample-item:first-of-type { border-top: 0; padding-top: 0; }
.sample-item h3 { margin: 0; }

/* No text-transform. A badge is a word the reader has to take in, not a
   decoration, and capitals cost them the word shape that makes it recognisable
   at a glance. The labels are written in the case they should render in, which
   also means what a test asserts is what a merchant sees. */
.tag {
  display: inline-block; margin-left: .4rem; padding: .05rem .45rem;
  font-size: .72rem;
  background: var(--accent-soft); color: var(--accent); border-radius: 999px;
  vertical-align: middle;
}
.tag.warn { background: var(--danger-bg); color: var(--danger); }

/* The unanswered count on a nav item.
   Its own class rather than .tag: a tag labels a value in the page, and this
   counts work waiting elsewhere, so it has to read at a glance from inside a
   nav row. Tokens rather than literal colours, so it follows the theme in both
   directions. The number is also given an aria-label in the template, because
   a bare digit beside "Support" is ambiguous read aloud. */
.badge {
  display: inline-block;
  min-width: 1.25rem;
  margin-left: .35rem;
  padding: .05rem .35rem;
  font-size: .72rem;
  font-variant-numeric: tabular-nums;
  text-align: center;
  background: var(--danger-bg);
  color: var(--danger);
  border-radius: 999px;
  vertical-align: middle;
}

/* A support message, which is the one place merchant-written prose is rendered.
   Newlines are preserved because it arrives from a textarea and a paragraph the
   merchant broke up should stay broken up; wrapping is forced because a pasted
   SKU list or URL would otherwise push the card wider than a phone. */
.ticket-body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Support's own replies, marked so a thread can be read at a glance rather than
   by reading every name. A left border rather than a fill, which keeps the
   contrast of the text itself untouched in both themes. */
.card.from-support {
  border-left: 3px solid var(--accent);
}

/* --------------------------------------------------------- vendor details */

/* Square object IDs, merchant IDs, SKUs. Monospace because these get compared
   character by character against a Square dashboard during support, where
   telling an O from a 0 is the whole job. */
code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .85em;
  background: var(--sunken);
  padding: .1rem .3rem;
  border-radius: 4px;
  overflow-wrap: anywhere;
}

/* A label-and-value list, for the facts about one connection or one item.
   Grid rather than a table: these are pairs, not rows, and at 52rem the two
   columns become one so a phone reads them stacked. */
.facts {
  display: grid;
  grid-template-columns: minmax(9rem, auto) 1fr;
  gap: .4rem 1rem;
  margin: 0 0 1rem;
}
.facts dt { color: var(--muted); font-size: .9rem; }
.facts dd { margin: 0; overflow-wrap: anywhere; }

@media (max-width: 52rem) {
  .facts { grid-template-columns: 1fr; gap: .1rem; }
  .facts dd { margin-bottom: .5rem; }
}

/* Onboarding instructions, collapsed. The custom app token costs the merchant
   about eight steps in their own Square admin, and that wall of text must not
   be the first thing on the page for the shops that are already connected. */
.help { margin-bottom: 1rem; }
/* The marker sits inside the content box, not outside it. A summary is a
   list-item, and the default `outside` paints its triangle in the margin, which
   here is the card's padding: the arrow hangs to the left of every heading,
   label and paragraph it is stacked with, and reads as misaligned rather than
   as indented. `inside` puts the whole row on the same edge as its siblings. */
.help > summary {
  cursor: pointer; color: var(--accent); font-size: .9rem;
  padding: .3rem 0; list-style-position: inside;
}
/* Both states are set, not just the closed one. Overriding ::marker content
   replaces the user agent's triangle, and the user agent's rotates on open by
   itself, so setting only the closed glyph leaves a control that looks
   identical whether it is open or shut. The trailing space is the gap to the
   label, which the marker no longer supplies once it is inside. */
/* Sized in em, so the marker scales with the label rather than being pinned to
   a pixel size that stops matching if the summary's font-size changes. The
   glyphs are the small triangles at U+25B8 and U+25BE; the larger U+25B6 and
   U+25BC read as solid blocks at this weight rather than as arrows. */
.help > summary::marker { content: "\25B8\00a0"; font-size: 1.35em; }
.help[open] > summary::marker { content: "\25BE\00a0"; font-size: 1.35em; }
.help > summary:focus-visible { outline: 2px solid var(--brand-dot); outline-offset: 2px; }
.help ol { margin: .5rem 0 0; padding-left: 1.3rem; color: var(--muted); font-size: .9rem; }
.help li { margin-bottom: .35rem; }

/* The preview's item table, with its columns pinned rather than measured.
   Auto layout sizes a column by its content, and Why is empty on every row that
   is merely an adoption, so on a catalog where nothing is wrong that column
   collapses to the width of its own heading and the two right columns end up
   huddled against the edge of a very wide Item column. Pinning the widths keeps
   the table looking the same whether or not anything needs attention, which is
   the state a merchant sees most. */
.preview-items { table-layout: fixed; }
.preview-items th:nth-child(1), .preview-items td:nth-child(1) { width: 32%; }
.preview-items th:nth-child(2), .preview-items td:nth-child(2) { width: 14%; }
.preview-items th:nth-child(3), .preview-items td:nth-child(3) { width: 26%; }
.preview-items th:nth-child(4), .preview-items td:nth-child(4) { width: 14%; }
.preview-items th:nth-child(5), .preview-items td:nth-child(5) { width: 14%; }

/* Fixed layout will not grow a cell to fit, so a long product name or a long
   reason has to be allowed to wrap rather than run out of its column. */
.preview-items td { overflow-wrap: anywhere; white-space: normal; }

/* Below the breakpoint the Why column is hidden by .opt, so the remaining two
   share the row rather than keeping the gap where it used to be. */
/* Why and Shopify are .opt, so a phone keeps Item, What happens and Compare.
   The three that remain are re-proportioned rather than left holding the gaps
   the hidden ones used to fill. */
@media (max-width: 52rem) {
  .preview-items th:nth-child(1), .preview-items td:nth-child(1) { width: 50%; }
  .preview-items th:nth-child(2), .preview-items td:nth-child(2) { width: 24%; }
  .preview-items th:nth-child(4), .preview-items td:nth-child(4) { width: 26%; }
}

/* An anchor that looks like a button.
   Styled to match button.small rather than sharing its rule, because the two
   are different elements with different defaults: an anchor has no padding, no
   border and an underline, so the shared look has to be stated rather than
   inherited. */
a.btn {
  display: inline-block; padding: .3rem .65rem;
  font-size: .85rem; font-weight: 550; white-space: nowrap;
  color: var(--accent); text-decoration: none;
  background: transparent; border: 1px solid var(--line); border-radius: 7px;
}
a.btn:hover { border-color: var(--accent); background: var(--sunken); }
a.btn:focus-visible { outline: 2px solid var(--brand-dot); outline-offset: 2px; }

/* The comparison tables. A changed row is marked by a tinted left edge rather
   than a background, because several rows on one product usually change at once
   and a page of filled rows is a page with no emphasis in it. */
.compare .changed { background: light-dark(rgba(2, 132, 199, .05), rgba(56, 189, 248, .06)); }
.compare .changed td:first-child { box-shadow: inset 3px 0 0 var(--accent-fill); }

/* The value being replaced. Struck through as well as dimmed: colour alone is
   not a difference a colour blind reader can see, which is the same reason the
   tags carry words rather than only a hue. */
.was { color: var(--muted); text-decoration: line-through; }

/* The question mark beside a label. Sized to sit on a line of text without
   changing its height, which is why it is not a .small button: those set a
   padding and a font size meant for a control, and one inside a table heading
   pushes the whole header row down. */
.help-icon {
  margin: 0 0 0 .3rem; padding: 0;
  width: 1.15rem; height: 1.15rem;
  display: inline-grid; place-items: center; vertical-align: -.15em;
  background: none; color: var(--muted); border: 0; border-radius: 999px;
}
/* An author `display` beats the user agent's `[hidden] { display: none }`, so
   without this the icon renders before the script has revealed it. Same trap
   as the theme toggle. */
.help-icon[hidden] { display: none; }
.help-icon:hover { color: var(--accent); filter: none; }
.help-icon:focus-visible { outline: 2px solid var(--brand-dot); outline-offset: 2px; }
.help-icon svg {
  width: 1.15rem; height: 1.15rem;
  fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round;
}

/* No padding on the dialog itself. The backdrop click test asks whether the
   dialog was the event target, and padding on the dialog is inside the box but
   outside the inner element, so a click on it would close a modal the user was
   aiming at. */
.help-modal {
  padding: 0;
  width: min(32rem, calc(100vw - 2rem));
  background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 10px 30px var(--shadow);

  /* Typography reset, and not a cosmetic one. A dialog paints in the top layer
     but it still inherits from where it sits in the document, and these sit
     inside the table heading they explain. The top layer changes what paints on
     top, not what inherits. Without these lines the whole explanation arrives in
     the heading's own .8rem uppercase letter-spaced grey, which is how it first
     shipped: three paragraphs of prose shouted at the reader. Anything placing a
     help icon inside a styled ancestor hits this again, so the reset lives here
     rather than at the one call site that exposed it. */
  color: var(--ink);
  font-size: 1rem; font-weight: 400; font-style: normal; line-height: 1.55;
  text-transform: none; text-align: left; letter-spacing: normal;
}
.help-modal::backdrop { background: light-dark(rgba(15, 23, 42, .35), rgba(0, 0, 0, .6)); }
.help-modal-inner { padding: 1.25rem; }
.help-modal h2 { margin-top: 0; }
.help-modal p { margin: 0 0 .75rem; }
.help-modal .hint { margin-bottom: 0; }

/* The vocabulary list. Terms are values a merchant sees rendered elsewhere in
   the interface, so they are set in the same face they appear in there. */
.help-terms { display: grid; grid-template-columns: auto 1fr; gap: .4rem 1rem; margin: 0 0 1rem; }
.help-terms dt { font-weight: 600; font-size: .9rem; }
.help-terms dd { margin: 0; color: var(--muted); font-size: .9rem; }

@media (max-width: 52rem) {
  .help-terms { grid-template-columns: 1fr; gap: .1rem; }
  .help-terms dd { margin-bottom: .5rem; }
}

/* The webhook URL is meant to be copied, not typed over. Still an input rather
   than a <p> so it can be selected with one click and read by a screen reader
   as the value it is. */
input[readonly] { background: var(--sunken); color: var(--muted); cursor: text; }

/* ------------------------------------------------------- layout utilities */

.row { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }

/* A group of buttons that belong together, packed from the left.
   Not .row: that spreads its children to the edges, which is right for a
   heading and a tag at opposite ends of a card and wrong for two buttons, where
   it puts the length of the card between the thing you meant to press and the
   thing you did not. Wraps rather than overflowing, because these sit next to a
   sentence of explanation and a phone has no room for both on one line. */
.actions {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: .6rem 1rem; margin-top: 1.1rem;
}
/* The buttons already carry their own top margin for when they follow a field
   directly. Inside this row the row owns the spacing, or the two stack up and
   the group sits too far from the field it belongs to.

   The transparent border is what makes a filled button and an outlined one the
   same height. Without it the outlined one is 2px taller, which is small enough
   to look like a rendering artefact and large enough to see. */
.actions button { margin-top: 0; border: 1px solid transparent; }
.actions button.secondary { border-color: var(--line); }
.right { text-align: right; }
.spaced { margin-top: 1rem; }
.hint { margin: .3rem 0 0; color: var(--muted); font-size: .8rem; }
.perms { margin: .4rem 0 0; padding-left: 1.1rem; color: var(--muted); font-size: .9rem; }


/* ============================================================ the dashboard */

/* Two columns: a fixed sidebar and everything else. The sidebar is its own
   scroll container and sticks, so a long members table does not scroll the
   navigation off the top of the screen. */
.shell {
  display: grid;
  grid-template-columns: 15.5rem 1fr;
  min-height: 100vh;
  /* dvh tracks the visible viewport as mobile browsers collapse their URL bar.
     vh does not, so a full-height sidebar overflows on first paint and leaves a
     dead scroll region at the bottom of every page. */
  min-height: 100dvh;
}

.side {
  display: flex; flex-direction: column; gap: .75rem;
  padding: 1rem .85rem;
  background: var(--panel);
  border-right: 1px solid var(--line);
  position: sticky; top: 0; height: 100vh; height: 100dvh; overflow-y: auto;
}
/* Aligns the wordmark's left edge with the switcher and the nav rows below it,
   while keeping the brand rule's clear space on the other three sides. */
.side .brand { padding: .35rem .3rem .5rem .65rem; }

.content { min-width: 0; }

/* ------------------------------------------------------ context switcher */

/* The whole point of the top left: which shop you are in, and the way to
   change it or cross into the admin area. */
.switcher { border: 1px solid var(--line); border-radius: var(--radius); background: var(--sunken); }
.switcher > summary {
  display: flex; align-items: center; gap: .5rem;
  padding: .5rem .65rem; cursor: pointer; list-style: none;
  border-radius: var(--radius);
}
.switcher > summary::-webkit-details-marker { display: none; }
.switcher > summary:hover { border-color: var(--accent); }
.switcher > summary:focus-visible { outline: 2px solid var(--brand-dot); outline-offset: 1px; }

.switcher-now { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.switcher-kind { font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.switcher-name {
  font-weight: 600; font-size: .95rem; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.chev { width: 1rem; height: 1rem; flex: none; fill: none; stroke: var(--muted); stroke-width: 2; stroke-linecap: round; }
.switcher[open] .chev { transform: rotate(180deg); }

.switcher-menu { padding: .25rem; border-top: 1px solid var(--line); }
.switcher-head {
  margin: .5rem .5rem .25rem; font-size: .7rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em; color: var(--muted);
}

/* One rule for the button rows and the anchor rows, so a form submit and a
   link are indistinguishable to the eye. */
.switcher-item {
  display: flex; align-items: baseline; justify-content: space-between; gap: .5rem;
  width: 100%; margin: 0; padding: .4rem .5rem;
  font: inherit; font-size: .9rem; text-align: left; text-decoration: none;
  background: transparent; color: var(--ink);
  border: 0; border-radius: 7px; cursor: pointer;
}
.switcher-item:hover { background: var(--accent-soft); color: var(--accent); filter: none; }
.switcher-item.current { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.switcher-item-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.switcher-role { font-size: .75rem; color: var(--muted); flex: none; }
.switcher-item:hover .switcher-role, .switcher-item.current .switcher-role { color: inherit; opacity: .8; }
.switcher-item .tag { margin-left: .35rem; }

/* --------------------------------------------------------------- left nav */

.side-nav { display: flex; flex-direction: column; gap: .1rem; }
.side-nav a {
  padding: .45rem .65rem; border-radius: 7px;
  color: var(--muted); text-decoration: none; font-size: .93rem;
}
.side-nav a:hover { background: var(--sunken); color: var(--ink); }

/* The current page is a filled row rather than only a colour change, because
   at this size a recoloured link is easy to miss. */
.side-nav a.on { background: var(--accent-soft); color: var(--accent); font-weight: 600; }

/* The group heading of an expanded section. Deliberately quieter than .on: the
   group says where you are and the child below says what you are looking at, so
   painting both as the current row leaves the reader with two highlights and no
   idea which one they clicked. */
.side-nav a.on.group { background: none; color: var(--ink); }

/* Indented by a border rather than by padding, so the rule lines up under the
   group and the child rows still start at the same text position when the
   highlight is filled. */
.side-subnav {
  display: flex; flex-direction: column; gap: .1rem;
  margin: .1rem 0 .1rem .85rem;
  padding-left: .5rem;
  border-left: 1px solid var(--line);
}
.side-subnav a { font-size: .9rem; }

.side-foot {
  margin-top: auto; padding-top: .75rem;
  border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: .5rem;
}
.side-foot .who {
  display: flex; align-items: center; gap: .4rem;
  font-size: .8rem;
}
/* The ellipsis is on the address rather than on the row: with the icon inside
   a flex line, truncating the container would shrink the glyph instead of the
   text it labels. */
.side-foot .who-email {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.who-icon {
  flex: none; width: .95rem; height: .95rem;
  fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round;
}
.side-actions { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }

/* ------------------------------------------------------------- narrow view */

/* Below this the sidebar stops being a sidebar and becomes a two row header:
   brand, switcher and actions on the first row, the nav as a scrolling strip on
   the second. A full height column on a phone leaves no room for the page it is
   navigating, and stacking it naively pushed the content most of a screen down.

   The DOM order does not change. Grid areas do the rearranging, so the markup
   stays in the order it reads in. */
@media (max-width: 52rem) {
  .shell { grid-template-columns: 1fr; display: block; }

  .side {
    position: static; height: auto; overflow: visible;
    border-right: 0; border-bottom: 1px solid var(--line);
    padding: .6rem .8rem;
    display: grid; gap: .5rem .7rem;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "brand switcher actions"
      "nav   nav      nav";
    align-items: center;
  }

  .side .brand { grid-area: brand; padding: 0; font-size: 1.15rem; }
  .switcher    { grid-area: switcher; }
  .side-foot   { grid-area: actions; margin: 0; padding: 0; border-top: 0; }
  .side-nav    { grid-area: nav; }

  /* The address is the longest string in the header and the least useful on a
     phone. It is still in the switcher's reach via sign out, so drop it here
     rather than letting it wrap the row. */
  .side-foot .who { display: none; }
  .side-actions { gap: .4rem; }

  /* A scrolling strip rather than a wrapping block, so the header height does
     not change with the number of items a role can see. */
  .side-nav {
    flex-direction: row; gap: .25rem;
    overflow-x: auto; scrollbar-width: none;
  }
  .side-nav::-webkit-scrollbar { display: none; }
  .side-nav a { white-space: nowrap; }

  /* display: contents dissolves the wrapper so its links become items of the
     scrolling row directly. A nested flex column here would be one tall cell in
     the middle of the strip, which is how a two row header becomes a three row
     one on the page a merchant uses most. The indent rule goes with it: there
     is no vertical list left to indent from. */
  .side-subnav { display: contents; }
  .side-subnav a { font-size: .93rem; }

  /* The group heading is redundant once its children are inline beside it, and
     the strip is the one place where every character costs. */
  .side-nav a.on.group { display: none; }

  /* The switcher menu would otherwise be as narrow as the summary above it. */
  .switcher[open] { position: relative; }

  main { padding: 1.25rem 1rem 3rem; }
  .card.narrow { margin: 1.5rem auto; }
  .banner { padding: .6rem 1rem; }
}

/* Touch. WCAG 2.2 puts the minimum target at 24px and the enhanced level at
   44px; pointer: coarse is the honest signal for which one applies, so the
   compact table buttons grow only where a finger is doing the pointing. */
@media (pointer: coarse) {
  button.small, a.btn, .switcher-item, .side-nav a, .theme-toggle {
    min-height: 44px;
  }
  a.btn { display: inline-flex; align-items: center; }
  button.small { display: inline-flex; align-items: center; }
  .theme-toggle { min-width: 44px; }
  td button.small { margin: .15rem 0; }

  /* The help icon is drawn at 18px so it does not disturb the line it sits on,
     which is under the 24px minimum for a touch target. The glyph stays that
     size and the hit area grows around it, so the row gets taller on a phone
     but nothing gets bigger to look at. */
  .help-icon { min-width: 44px; min-height: 44px; margin-left: 0; }

  /* A crumb is a line of text, and text cannot be 44px tall without shouting.
     The hit area grows into the gap above the heading instead, which is space
     the trail already owns. The margin gives back what the padding takes, so
     the gap below the trail is the same on a phone as anywhere else. */
  .crumbs a { display: inline-block; padding: .55rem 0; }
  .crumbs { margin-bottom: .3rem; }
}

/* An email address on a public page. The trap is what a scraper reads and a
   person never does: display:none keeps it out of the accessibility tree and
   out of a copied selection, so selecting the address still pastes the real
   one. Not a link, because a mailto href is the first thing harvested. */
.mail { overflow-wrap: anywhere; }
.mail-trap { display: none; }
.mail-line { margin: .75rem 0 0; }
