/* Relay admin portal. Hand-rolled: no framework, no CDN, no dependencies. */

:root {
  --bg: #f6f7f9;
  --panel: #fff;
  --ink: #1c2024;
  --muted: #667085;
  --line: #dfe3e8;
  --accent: #2b6cb0;
  --ok: #1f7a4d;
  --warn: #9a6700;
  --bad: #b42318;
  --ok-bg: #e7f5ee;
  --warn-bg: #fdf6e3;
  --bad-bg: #fdeceb;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  background: var(--bg);
}

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

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

header.top {
  background: #1c2024;
  color: #fff;
  padding: 0 20px;
  display: flex;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
}
header.top .brand { font-weight: 600; padding: 14px 0; letter-spacing: .3px; }
header.top nav { display: flex; gap: 4px; flex-wrap: wrap; }
header.top nav a {
  color: #c9ced6;
  text-decoration: none;
  padding: 14px 12px;
  border-bottom: 3px solid transparent;
}
header.top nav a:hover { color: #fff; }
header.top nav a.on { color: #fff; border-bottom-color: var(--accent); }

/* ---------- Setup Wizard ---------- */

/* The one nav item that is a guided task rather than a place to look at, so it
   gets a wand and a soft twinkle. Kept quiet on purpose: it sits in a header
   that is on screen all day, and anything brighter would become an irritation
   by the afternoon rather than an invitation. */
header.top nav a.wizard {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: #e8ecf2;
}
header.top nav a.wizard:hover { color: #fff; }

.wand {
  position: relative;
  display: inline-flex;
  width: 15px;
  height: 15px;
  flex: none;
  color: #ffd76e;
}
.wand svg { width: 15px; height: 15px; }

/* Three dots of light around the wand, each a soft glow rather than a hard pixel.
   Staggered so they never pulse in unison -- simultaneous blinking reads as a
   loading indicator, which is the wrong message entirely. */
.spark {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #fff6dc;
  /* A wide, faint halo rather than a tight bright one -- the glow is most of what
     is seen at this size, and a hard edge on it makes the dot read as a stray
     pixel instead of a light. */
  box-shadow: 0 0 7px 2px rgba(255, 214, 110, .5);
  opacity: 0;
  animation: wand-spark 6s ease-in-out infinite;
}
.spark--a { top: -3px; left: 11px; animation-delay: 0s; }
.spark--b { top: 4px;  left: -4px; animation-delay: 2s; }
.spark--c { top: 11px; left: 8px;  animation-delay: 4s; }

/* Roughly 1.7s to come up and go again, out of a 6s cycle. The rest of the time
   it is dark. Slow enough to read as a glimmer rather than a blink. */
@keyframes wand-spark {
  0%, 100% { opacity: 0; transform: scale(.6); }
  14%      { opacity: .8; transform: scale(1); }
  28%      { opacity: 0; transform: scale(.6); }
}

/* Some people get motion sick, and a header is not worth that. The wand stays;
   only the movement stops. */
@media (prefers-reduced-motion: reduce) {
  .spark { animation: none; opacity: .55; transform: none; }
}
header.top .right { margin-left: auto; display: flex; align-items: center; gap: 12px; }
header.top .who { color: #8d95a0; font-size: 13px; }
header.top form { margin: 0; }

/* ---------- account menu ---------- */

.usermenu { position: relative; }

.usermenu > summary {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 10px 7px 7px;
  border-radius: 999px;
  cursor: pointer;
  color: #c9ced6;
  font-size: 13px;
  font-weight: 600;
  list-style: none;          /* Firefox */
  user-select: none;
}
.usermenu > summary::-webkit-details-marker { display: none; }   /* Safari/Chrome */
.usermenu > summary:hover,
.usermenu[open] > summary,
.usermenu[data-active] > summary { background: #2a2f36; color: #fff; }
.usermenu > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.usermenu__avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  flex: none;
}

/* A chevron drawn with a rotated border, so the menu needs no icon font and no
   second asset request. */
.usermenu__chev {
  width: 6px;
  height: 6px;
  margin-left: 2px;
  border-right: 1.6px solid currentcolor;
  border-bottom: 1.6px solid currentcolor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform .15s ease;
}
.usermenu[open] .usermenu__chev { transform: translateY(1px) rotate(225deg); }

.usermenu__panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 40;
  min-width: 208px;
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(16, 24, 40, .18);
}
.usermenu__panel a,
.usermenu__panel button {
  display: block;
  width: 100%;
  padding: 9px 11px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.usermenu__panel a:hover,
.usermenu__panel button:hover { background: #eef1f5; }
.usermenu__panel a.on { color: var(--accent); font-weight: 600; }
/* Sign out is a different kind of action from the four links above it. */
.usermenu__panel form { border-top: 1px solid var(--line); margin-top: 6px; padding-top: 6px; }
.usermenu__panel button { color: var(--bad); }

main { max-width: 1180px; margin: 24px auto 60px; padding: 0 20px; }

h1 { font-size: 22px; margin: 0 0 4px; }
h2 { font-size: 16px; margin: 28px 0 10px; }
p.sub { color: var(--muted); margin: 0 0 20px; }

/* ---------- panels and tables ---------- */

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 18px 20px;
  margin-bottom: 18px;
}

table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
th { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); }
tr:last-child td { border-bottom: 0; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); font-size: 13px; }
.muted { color: var(--muted); }
.wrap { max-width: 420px; overflow-wrap: anywhere; }

/* ---------- utilities ----------
   These exist so the views carry no inline style attributes. The page's
   Content-Security-Policy is style-src 'self' with no 'unsafe-inline', and
   style-src-attr falls back to it -- so a style="..." attribute in markup is
   liable to be dropped by the browser, silently and only in cosmetic ways.
   Adding 'unsafe-inline' would have been the easy fix and the wrong one: these
   pages render Trello-authored text, and that directive is a real part of what
   contains it. */

.fs-13 { font-size: 13px; }
.fs-12 { font-size: 12px; }
.fw-normal { font-weight: 400; }
.mt-0 { margin-top: 0; }
.mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; }
.mt-18 { margin-top: 18px; }
.mt-20 { margin-top: 20px; }
.mb-0 { margin-bottom: 0; }
.note-centred { max-width: 640px; margin: 12px auto 0; }

/* Off-screen but still in the accessibility tree. For a label whose text the
   sighted layout already makes obvious -- display:none or hidden would drop it
   from the tree too, which is the whole thing being avoided. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- status ---------- */

.pill {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.pill.ok { background: var(--ok-bg); color: var(--ok); }
.pill.warn { background: var(--warn-bg); color: var(--warn); }
.pill.bad { background: var(--bad-bg); color: var(--bad); }
.pill.idle { background: #eef0f3; color: var(--muted); }

/* ---------- forms ---------- */

label { display: block; font-weight: 600; font-size: 13px; margin: 14px 0 5px; }
label .hint { font-weight: 400; color: var(--muted); }

input[type=text], input[type=password], input[type=number], input[type=email], select, textarea {
  width: 100%;
  max-width: 520px;
  padding: 8px 10px;
  border: 1px solid #c6ccd4;
  border-radius: 6px;
  font: inherit;
  background: #fff;
}
input[readonly] { background: #f0f2f5; color: var(--muted); }
textarea { min-height: 90px; }

/* A value the system derives rather than asks for. Reads as data, not as a
   disabled input — there is nothing to type, so it should not look like a field. */
.derived {
  margin: 0 0 4px;
  padding: 8px 10px;
  background: #eef0f3;
  border: 1px solid var(--line);
  border-radius: 6px;
  max-width: 520px;
  overflow-wrap: anywhere;
  color: var(--ink);
}

/* For a value that must be read as one string -- a URL broken across two lines
   is hard to check against what is registered, and checking it character by
   character is exactly what you do when a signature fails. Scrolls rather than
   wraps if it genuinely will not fit. */
.derived--wide {
  max-width: 100%;
  white-space: nowrap;
  overflow-x: auto;
  overflow-wrap: normal;
}

/* A fixed, non-editable prefix welded to an input — used for the https:// on the
   Teamwork site field, so the scheme cannot be typed wrong or typed twice. */
.prefixed { display: flex; max-width: 520px; }
.prefixed .prefix {
  display: flex;
  align-items: center;
  padding: 8px 10px;
  border: 1px solid #c6ccd4;
  border-right: 0;
  border-radius: 6px 0 0 6px;
  background: #eef0f3;
  color: var(--muted);
  font-family: var(--mono);
  font-size: 13px;
  white-space: nowrap;
  user-select: none;
}
.prefixed input { border-radius: 0 6px 6px 0; max-width: none; flex: 1; min-width: 0; }

/* Mutually exclusive choices where each needs a sentence of explanation --
   picking an authentication mode, not ticking a box. Overrides the bold block
   `label` above, which is sized for a field caption rather than a paragraph. */
label.radio {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 10px;
  align-items: start;
  max-width: 520px;
  margin: 8px 0 0;
  padding: 10px 12px;
  border: 1px solid #c6ccd4;
  border-radius: 6px;
  font-weight: 400;
  font-size: 14px;
  cursor: pointer;
}
label.radio:hover { border-color: #9aa4b1; }
label.radio input[type=radio] { margin-top: 3px; }
label.radio strong { font-size: 14px; }
label.radio span { grid-column: 2; }
label.radio:has(input:checked) { border-color: var(--accent); background: #f4f8fd; }

/* Single-purpose pages that are one short message wide -- the OAuth hops. */
.panel.narrow { max-width: 520px; }

/* ---------- connection form ---------- */

/* Source and destination are two parallel things, and the old single column made
   them read as eight sequential ones -- hiding the fact that this page describes
   a flow from somewhere to somewhere else. */
.connform {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: start;
  gap: 0;
  margin-bottom: 18px;
}

.connform__side {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 20px 22px 24px;
  min-width: 0;   /* lets long select values shrink instead of forcing the grid wider */
}

.connform__head {
  display: flex;
  align-items: center;
  gap: 13px;
  padding-bottom: 14px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}
.connform__head .toolmark { width: 40px; height: 40px; border-radius: 10px; }
.connform__title { margin: 0; font-size: 17px; letter-spacing: -.2px; }
.connform__sub { margin: 2px 0 0; font-size: 12.5px; color: var(--muted); }

/* The arrow between the two columns. Horizontal on a wide screen, rotated to
   point down when the columns stack. */
/* Level with the two headers, not centred between the columns. The columns are
   very different heights -- Source has two fields, Destination has five plus the
   build panel -- so a vertically centred arrow ends up floating in empty space
   pointing at nothing. */
.connform__arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  align-self: start;
  padding: 0 14px;
  margin-top: 40px;
}
.connform__arrow-line {
  width: 34px;
  height: 2px;
  background: linear-gradient(90deg, var(--line), var(--accent));
  border-radius: 2px;
}
.connform__arrow-head {
  width: 0;
  height: 0;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  border-left: 9px solid var(--accent);
}

/* ---------- board preview ---------- */

/* The source board drawn as a board. Fills what was a large void under two
   dropdowns, and earns its place: it is the only chance to confirm the board you
   picked is the board you meant before columns get created from it. */
.boardprev {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}
.boardprev__head {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .3px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 10px;
}
.boardprev__count {
  display: inline-block;
  min-width: 20px;
  padding: 1px 6px;
  margin-right: 4px;
  border-radius: 5px;
  background: var(--accent);
  color: #fff;
  font-size: 12px;
  text-align: center;
}
/* 140px: the name and the card count share a line, and at 112 a word like
   "Complete" beside a three-digit count broke mid-word. 140 gives three tiles per
   column on a normal screen with room to spare. */
.boardprev__cols {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
}
.boardprev__col {
  background: #fafbfc;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 9px 9px;
  min-width: 0;
}
/* The pickable variant, used only while Relay is going to build the board. The
   whole tile is the label, so the hit target is the tile and not just the 13px
   checkbox. */
.boardprev__col--pick {
  cursor: pointer;
  display: block;
}
.boardprev__col--pick:hover {
  border-color: var(--accent);
}
/* Unticked reads as "this one is being left out" at a glance across the whole
   board, which is the check somebody makes before pressing create. */
.boardprev__col--pick:has(input:not(:checked)) {
  opacity: .45;
}
.boardprev__col--pick:has(input:not(:checked)) .boardprev__name {
  text-decoration: line-through;
}
.boardprev__col--pick input[type="checkbox"] {
  margin: 0 6px 0 0;
  flex: none;
  vertical-align: middle;
}
.boardprev__bar {
  display: block;
  height: 4px;
  border-radius: 2px;
  margin-bottom: 8px;
  background: #c3c9d2;
}
/* Name left, card count right. baseline alignment so the number sits on the same
   line as the first line of a name that wraps to two. */
/* Name left, count right, on the line under the bar. */
.boardprev__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.boardprev__name {
  display: block;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  /* break-word, not anywhere: prefer wrapping between words and only split one
     as a last resort. "In Progress" becomes two lines, not "In Progres/s". */
  overflow-wrap: break-word;
}
.boardprev__count-badge {
  flex: none;
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* Numbered, not inline styles: style-src is 'self' with no 'unsafe-inline', so a
   style attribute here is dropped and the swatches vanish. These MUST stay in the
   same order as PROVISION_STAGE_COLORS in twsync/provisioning.php -- that is what
   makes this a preview of the colours that will actually be created. */
.boardprev__bar--0 { background: #4a90d9; }
.boardprev__bar--1 { background: #6fbf73; }
.boardprev__bar--2 { background: #f0ad4e; }
.boardprev__bar--3 { background: #d9534f; }
.boardprev__bar--4 { background: #9b59b6; }
.boardprev__bar--5 { background: #1abc9c; }
.boardprev__bar--6 { background: #e67e22; }
.boardprev__bar--7 { background: #607d8b; }
/* The source's Backlog: not a column, so not one of the build palette's colours,
   and dashed so it does not read as something that will be created. */
.boardprev__bar--backlog { background: #c3c9d2; }
.boardprev__col--backlog { border-style: dashed; }

/* A quieter notice for a statement of fact rather than a warning. */
.notice.ok-quiet {
  background: var(--ok-bg);
  border-color: #bfe3d0;
  color: var(--ok);
  padding: 9px 12px;
  margin-bottom: 12px;
}

/* ---------- radio cards ---------- */

/* One choice between three siblings, so they sit abreast. Stacked, they read as
   three separate questions and took three times the height. */
.radiocards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin: 10px 0 4px;
}

.radiocard {
  display: block;
  position: relative;
  margin: 0;
  padding: 11px 11px 12px 11px;
  border: 1px solid #c6ccd4;
  border-radius: 9px;
  font-weight: 400;
  cursor: pointer;
  transition: border-color .14s ease, background .14s ease;
}
.radiocard:hover { border-color: #9aa4b1; }

/* The native control is hidden from sight but NOT from the accessibility tree or
   the keyboard -- it still receives focus, still announces, still toggles. The
   card itself is the visible control. */
.radiocard input[type=radio] {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}
.radiocard__title {
  display: block;
  font-size: 13px;
  font-weight: 650;
  line-height: 1.3;
  color: var(--ink);
  margin-bottom: 3px;
}
.radiocard__note {
  display: block;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--muted);
}

.radiocard:has(input:checked) {
  border-color: var(--accent);
  background: #f4f8fd;
  box-shadow: inset 0 0 0 1px var(--accent);
}
.radiocard:has(input:checked) .radiocard__title { color: var(--accent); }

/* Focus has to be visible on the card, because the input it belongs to is not. */
.radiocard:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

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

.connform__client { margin-bottom: 18px; }
.connform__client select { max-width: 100%; }
.connform__side select,
.connform__side input[type=text],
.connform__side input[type=number] { max-width: 100%; }

/* Identity: the two values the system derives. Side by side, subdued, and below
   the form -- at the top they read as the first things to fill in, which is the
   opposite of true. */
.identity__grid {
  display: grid;
  grid-template-columns: minmax(0, 220px) minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}
.identity__label {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .3px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 5px;
}

/* A disclosure for something needed on a bad day and never on a good one. */
.details-quiet > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  list-style: none;
}
.details-quiet > summary::-webkit-details-marker { display: none; }
.details-quiet > summary::before {
  content: "";
  width: 5px;
  height: 5px;
  border-right: 1.5px solid currentcolor;
  border-bottom: 1.5px solid currentcolor;
  transform: rotate(-45deg);
  transition: transform .15s ease;
}
.details-quiet[open] > summary::before { transform: rotate(45deg); }
.details-quiet > summary:hover { color: var(--ink); }

.connform__save {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
}
.connform__active { margin: 0; font-size: 14px; }

/* One column on anything narrow. The arrow turns to point down, so the flow still
   reads correctly rather than pointing sideways at nothing. */
@media (max-width: 900px) {
  .connform { grid-template-columns: 1fr; }
  .connform__arrow { padding: 14px 0; transform: rotate(90deg); }
  .identity__grid { grid-template-columns: 1fr; }
}

/* ---------- platform grid (Settings) ---------- */

.toolgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}

.toolcard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 18px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 20px;
  text-decoration: none;
  color: var(--ink);
  overflow: hidden;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}

/* A wash of the platform's own colour, bled in from behind the mark. Keeps the
   four cards distinguishable at a glance without four saturated blocks fighting
   each other -- the mark is the accent, this is only its shadow. */
.toolcard::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  width: 210px;
  height: 165px;
  /* A gradient that fades to nothing, not a circle with an edge. A hard-edged
     disc reads as a rendering fault where it crosses the text. */
  background: radial-gradient(115px 105px at 100% 0%, currentcolor, transparent 70%);
  opacity: .14;
  pointer-events: none;
  transition: opacity .16s ease;
}
.toolcard--trello   { color: #0079bf; }
.toolcard--teamwork { color: #e02020; }
.toolcard--asana    { color: #f06a6a; }
.toolcard--monday   { color: #6161ff; }
.toolcard--jira     { color: #2684ff; }

.toolcard:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(16, 24, 40, .09);
  border-color: currentcolor;
}
.toolcard:hover::before { opacity: .24; }

.toolcard__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  position: relative;
}
.toolcard__title { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.toolcard__name {
  font-weight: 650;
  font-size: 18px;
  letter-spacing: -.2px;
  color: var(--ink);
}
.toolcard__note { font-size: 12px; line-height: 1.45; color: var(--muted); }

/* Status sits under a hairline at the foot, out of the way of the name. */
.toolcard__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: auto;
  padding-top: 13px;
  border-top: 1px solid var(--line);
  position: relative;
}
.toolcard__state {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .3px;
  text-transform: uppercase;
  color: var(--muted);
}
.dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.dot--ok   { background: var(--ok); box-shadow: 0 0 0 3px var(--ok-bg); }
.dot--warn { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-bg); }
.dot--idle { background: #c3c9d2; }

/* On the Settings cards the state is the thing being scanned for -- which
   platforms are ready -- so Configured is made to stand out and Not set to
   recede: a larger haloed dot and a coloured label, against a hollow ring and a
   faded one. Scoped here because .dot is shared with quieter places. */
.toolcard__state .dot { width: 10px; height: 10px; }
.toolcard__state--ok { color: var(--ok); font-weight: 700; }
.toolcard__state--ok .dot { box-shadow: 0 0 0 4px var(--ok-bg); }
.toolcard__state--warn { color: var(--warn); }
.toolcard__state--idle { font-weight: 500; opacity: .7; }
.toolcard__state--idle .dot { width: 8px; height: 8px; background: transparent; border: 1.5px solid #c3c9d2; }

.toolcard__go {
  font-size: 15px;
  color: var(--muted);
  transition: transform .16s ease, color .16s ease;
}
.toolcard:hover .toolcard__go { transform: translateX(3px); color: currentcolor; }

/* The platform's mark: an inline SVG where the real logo is plain geometry, a
   two-letter tile where it is not. Both are 44px so the grid stays even. */
.toolmark {
  width: 54px;
  height: 54px;
  border-radius: 13px;
  flex: none;
  box-shadow: 0 2px 6px rgba(16, 24, 40, .12);
}

.toolmark--letters {
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 19px;
  letter-spacing: .5px;
  color: #fff;
  user-select: none;
  /* A DEFAULT background, not just per-tool ones. The text is white, so a tool
     with no colour of its own rendered white-on-nothing -- an empty tile that
     looked like a broken image rather than a tool without a logo. Any tool added
     later is legible from the moment it exists. */
  background: #6b7280;
}
.toolmark--teamwork { background: #e02020; }
.toolmark--jira     { background: #2684ff; }

button, .btn {
  font: inherit;
  font-weight: 600;
  padding: 8px 14px;
  border-radius: 6px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}
button.secondary, .btn.secondary { background: #fff; color: var(--ink); border-color: #c6ccd4; }
button.danger { background: var(--bad); border-color: var(--bad); }
button.small, .btn.small { padding: 4px 10px; font-size: 13px; }
.actions { margin-top: 20px; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
form.inline { display: inline; margin: 0; }
/* The sign-in section on an account page: a heading with its status pill, then
   numbered steps, so "what do I press, and in what order" answers itself. */
.signin__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-top: 28px; }
.signin__head h2 { margin: 0; }
.signin { max-width: 680px; margin: 14px 0 6px; }
.steps { list-style: none; counter-reset: step; margin: 0; padding: 0; }
.steps > li {
  counter-increment: step;
  position: relative;
  padding: 2px 0 18px 42px;
}
.steps > li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-weight: 700;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* The rail joining the numbers, so the steps read as one sequence. */
.steps > li:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 13px;
  top: 32px;
  bottom: 4px;
  width: 2px;
  background: var(--line);
}
.steps > li > label:first-child { margin-top: 3px; }
.steps .prefixed { max-width: 520px; }
.steps p { margin: 6px 0 0; }
/* Method tabs. The radios are never submitted and sit outside every form;
   :has() shows the panel whose radio is checked, so no JavaScript is needed. */
.signin__using { margin: 6px 0 12px; }
.methodtabs { max-width: 720px; }
.methodtabs__radio { position: absolute; opacity: 0; pointer-events: none; }
.methodtabs__bar { display: flex; gap: 4px; border-bottom: 1px solid var(--line); }
.methodtabs__tab {
  margin: 0;
  padding: 9px 14px;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: 8px 8px 0 0;
  margin-bottom: -1px;
}
.methodtabs__tab:hover { color: var(--ink); }
.methodtabs__tab .pill { margin-left: 4px; font-size: 11px; }
.methodtabs__panel { display: none; border: 1px solid var(--line); border-top: 0; border-radius: 0 0 10px 10px; padding: 18px 18px 4px; }
.methodtabs:has(#tab-signin:checked) .methodtabs__panel--signin,
.methodtabs:has(#tab-keys:checked) .methodtabs__panel--keys { display: block; }
.methodtabs:has(#tab-signin:checked) label[for="tab-signin"],
.methodtabs:has(#tab-keys:checked) label[for="tab-keys"] {
  color: var(--ink);
  background: var(--panel);
  border-color: var(--line);
}
.methodtabs:has(#tab-signin:focus-visible) label[for="tab-signin"],
.methodtabs:has(#tab-keys:focus-visible) label[for="tab-keys"] { outline: 2px solid var(--accent); outline-offset: 2px; }
.methodtabs__panel .signin { margin: 0; }
.keyrow { display: flex; gap: 8px; align-items: center; max-width: 520px; }
.keyrow input { flex: 1; min-width: 0; }

/* ---------- notices ---------- */

.notice { padding: 10px 14px; border-radius: 6px; margin-bottom: 18px; border: 1px solid; }
.notice.ok { background: var(--ok-bg); border-color: #bfe3d0; color: var(--ok); }
.notice.bad { background: var(--bad-bg); border-color: #f3c4c0; color: var(--bad); }
.notice.warn { background: var(--warn-bg); border-color: #ecd9a8; color: var(--warn); }
/* The confirmation inside a warning: a plain line, not a form label's block. */
.notice.warn label { display: block; font-weight: 600; color: var(--ink); }

/* ---------- dashboard ---------- */

/* Status colours are RESERVED for state: synced, waiting, failed. Client
   identity is carried by logos, never by a hue, so no categorical palette is
   needed and none of these ever means "series 4". Each is paired with an icon
   or a label wherever it appears. */
:root {
  --dash-ok: #17915a;
  --dash-warn: #e0a019;
  --dash-bad: #d03b3b;
  --dash-idle: #98a2b3;
  --dash-navy: #14213d;
  --dash-shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 4px 14px rgba(16, 24, 40, .06);
}

.dash-h2 { font-size: 15px; margin: 0; letter-spacing: -.1px; }
.dash-h2--section { margin: 28px 0 12px; font-size: 17px; }

.dash-panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 18px 20px;
  box-shadow: var(--dash-shadow);
  margin-bottom: 18px;
  min-width: 0;
}
.dash-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

/* Hero: the answer to "is everything OK?" before anything else is read. */
.dash-hero {
  position: relative;
  overflow: hidden;
  border-radius: 16px;
  padding: 24px;
  margin-bottom: 18px;
  color: #fff;
  background: linear-gradient(135deg, var(--dash-navy) 0%, #1e3a6e 55%, var(--accent) 100%);
  box-shadow: var(--dash-shadow);
}
.dash-hero--bad { background: linear-gradient(135deg, #3b1219 0%, #6e1e2a 55%, #b42318 100%); }
.dash-hero::after {
  content: "";
  position: absolute;
  right: -80px;
  top: -80px;
  width: 260px;
  height: 260px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, .14), rgba(255, 255, 255, 0) 70%);
  pointer-events: none;
}
.dash-hero__status { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 20px; }
.dash-hero__title { font-size: 24px; margin: 0; letter-spacing: -.3px; }
.dash-hero__lede { margin: 4px 0 0; color: rgba(255, 255, 255, .78); font-size: 14px; }
.dash-hero--empty .dash-hero__status { margin-bottom: 0; display: block; max-width: 620px; }
.dash-btn-light { background: #fff; color: var(--dash-navy); border-color: #fff; }

.dash-pulse {
  flex: none;
  width: 14px;
  height: 14px;
  margin-top: 8px;
  border-radius: 50%;
  background: #4ade80;
  box-shadow: 0 0 0 0 rgba(74, 222, 128, .6);
  animation: dash-pulse 2.4s ease-out infinite;
}
.dash-hero--bad .dash-pulse { background: #fca5a5; box-shadow: 0 0 0 0 rgba(252, 165, 165, .6); }
@keyframes dash-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(255, 255, 255, .45); }
  70%  { box-shadow: 0 0 0 12px rgba(255, 255, 255, 0); }
  100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}

.dash-chip {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .16);
  font-size: 12px;
}
.dash-chip--muted { background: #eef0f3; color: var(--muted); }

.dash-kpis {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 12px;
  position: relative;
  z-index: 1;
}
.dash-kpi {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px 16px;
  border-radius: 12px;
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .16);
  color: #fff;
  text-decoration: none;
  min-width: 0;
}
.dash-kpi--link:hover { background: rgba(255, 255, 255, .18); }
.dash-kpi--alert { background: rgba(208, 59, 59, .35); border-color: rgba(255, 180, 180, .5); }
.dash-kpi__label { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: rgba(255, 255, 255, .72); }
.dash-kpi__value { font-size: 28px; line-height: 1.15; font-variant-numeric: tabular-nums; letter-spacing: -.5px; }
.dash-kpi__cap { font-size: 12px; color: rgba(255, 255, 255, .72); line-height: 1.35; }
.dash-kpi .dash-spark { width: 100%; height: 26px; margin: 2px 0; }
.dash-trend { font-size: 13px; font-weight: 600; }
.dash-trend--up { color: #86efac; }
.dash-trend--down { color: #fecaca; }

/* Needs attention: one line per problem, each ending in the button that fixes it. */
.dash-attention { border-left: 4px solid var(--dash-bad); }
.dash-issues { list-style: none; margin: 10px 0 0; padding: 0; }
.dash-issue {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.dash-issue:first-child { border-top: 0; }
.dash-issue__text { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: 2px 8px; align-items: baseline; }
.dash-issue__msg { flex-basis: 100%; font-size: 13.5px; color: var(--ink); }

/* Icons: status shape + colour together. */
.dash-icon { width: 16px; height: 16px; flex: none; vertical-align: -3px; }
.dash-icon .i-mark { fill: none; stroke: #fff; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.dash-issue--bad .i-fill, .dash-conn--bad .i-fill, .dash-feed__status--bad .i-fill { fill: var(--dash-bad); }
.dash-issue--warn .i-fill, .dash-conn--warn .i-fill, .dash-feed__status--warn .i-fill { fill: var(--dash-warn); }
.dash-conn--ok .i-fill, .dash-feed__status--ok .i-fill { fill: var(--dash-ok); }
.dash-conn--idle .i-fill, .dash-feed__status--idle .i-fill { fill: var(--dash-idle); }

/* Activity chart + busiest clients. */
.dash-row { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: 18px; }
.dash-row .dash-panel { margin-bottom: 18px; }

.dash-legend { list-style: none; display: flex; gap: 14px; margin: 0; padding: 0; font-size: 12.5px; color: var(--muted); }
.dash-legend li { display: inline-flex; align-items: center; gap: 6px; }
.dash-swatch { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.dash-swatch.c-ok { background: var(--dash-ok); }
.dash-swatch.c-warn { background: var(--dash-warn); }
.dash-swatch.c-bad { background: var(--dash-bad); }

.dash-chart { display: block; width: 100%; height: auto; }
.dash-chart .c-ok { fill: var(--dash-ok); }
.dash-chart .c-warn { fill: var(--dash-warn); }
.dash-chart .c-bad { fill: var(--dash-bad); }
.dash-chart .c-empty { fill: var(--line); }
.dash-chart .c-grid { stroke: #eef0f3; stroke-width: 1; }
.dash-chart .c-axis { stroke: #c6ccd4; stroke-width: 1; }
.dash-chart .c-label { fill: var(--muted); font-size: 11px; }
.dash-chart .c-hit { fill: transparent; }
.dash-chart .c-bar:hover .c-hit { fill: rgba(43, 108, 176, .07); }

.dash-table { margin-top: 8px; font-size: 13px; }
.dash-table > summary { cursor: pointer; color: var(--muted); }
.dash-table table { margin-top: 8px; }

.dash-hbars { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 12px; }
.dash-hbars li {
  display: grid;
  grid-template-columns: 24px minmax(0, 7em) minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
}
.dash-hbars__name { font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-hbars__value { font-variant-numeric: tabular-nums; font-size: 13.5px; }
.dash-hbar { display: block; width: 100%; height: 10px; border-radius: 999px; overflow: hidden; }
.dash-hbar .c-track { fill: #eef0f3; }
.dash-hbar .c-fill { fill: var(--accent); }

/* Client cards, worst first. */
.dash-clients {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 16px;
  margin-bottom: 20px;
}
.dash-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-top: 4px solid var(--dash-ok);
  border-radius: 12px;
  padding: 16px 18px;
  box-shadow: var(--dash-shadow);
  transition: transform .15s ease, box-shadow .15s ease;
  min-width: 0;
}
.dash-card:hover { transform: translateY(-2px); box-shadow: 0 2px 4px rgba(16, 24, 40, .06), 0 10px 24px rgba(16, 24, 40, .1); }
.dash-card--warn { border-top-color: var(--dash-warn); }
.dash-card--bad { border-top-color: var(--dash-bad); }
.dash-card--idle { border-top-color: var(--dash-idle); }

.dash-card__head { display: flex; align-items: center; gap: 12px; }
.dash-card__head .cavatar--lg { width: 52px; height: 52px; font-size: 18px; }
/* A logo keeps its own proportions on a white plate; initials keep their hue. */
.dash-card__head img.cavatar--lg { object-fit: contain; background: #fff; }
.dash-card__title { flex: 1; min-width: 0; }
.dash-card__title h3 { margin: 0; font-size: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-card__last { font-size: 12.5px; color: var(--muted); }

.dash-ring { width: 56px; height: 56px; flex: none; }
.dash-ring .c-track { fill: none; stroke: #eef0f3; stroke-width: 6; }
.dash-ring .c-arc { fill: none; stroke: var(--dash-ok); stroke-width: 6; stroke-linecap: round; }
.dash-ring--warn .c-arc { stroke: var(--dash-warn); }
.dash-ring--bad .c-arc { stroke: var(--dash-bad); }
.dash-ring--idle .c-arc { stroke: var(--dash-idle); }
.dash-ring .c-ringtext { font-size: 13px; font-weight: 700; fill: var(--ink); }
.dash-ring .c-ringpct { font-size: 8px; font-weight: 600; fill: var(--muted); }

.dash-card__trend { display: flex; align-items: center; gap: 10px; margin: 12px 0 4px; }
.dash-card__trend .dash-spark { flex: 1; height: 34px; min-width: 0; }

.dash-spark { display: block; }
.dash-spark .c-stroke { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.dash-spark .c-area { stroke: none; }
.c-line .c-stroke { stroke: var(--accent); }
.c-line .c-area { fill: rgba(43, 108, 176, .1); }
.c-line .c-dot { fill: var(--accent); stroke: #fff; stroke-width: 2; }
.c-line-light .c-stroke { stroke: #fff; }
.c-line-light .c-area { fill: rgba(255, 255, 255, .14); }
.c-line-light .c-dot { fill: #fff; }

.dash-card__stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 10px 0 0; }
.dash-card__stats div { background: #f6f7f9; border-radius: 8px; padding: 8px 10px; }
.dash-card__stats dt { font-size: 11px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); }
.dash-card__stats dd { margin: 0; font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.dash-card__stats dd.is-bad { color: var(--bad); }
.dash-card__note { font-size: 11.5px; color: var(--muted); margin: 6px 0 0; }

.dash-conns { list-style: none; margin: 12px 0 0; padding: 0; }
.dash-conn {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0 0;
  margin-top: 10px;
  border-top: 1px solid var(--line);
}
.dash-conn__mark .toolmark { width: 28px; height: 28px; border-radius: 7px; }
.dash-conn__body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.dash-conn__slug { font-family: var(--mono); font-size: 12.5px; }
.dash-conn__msg { font-size: 13px; display: inline-flex; gap: 6px; align-items: flex-start; }
.dash-conn__msg .dash-icon { margin-top: 2px; }

/* Live feed. */
.dash-feed__list { list-style: none; margin: 0; padding: 0; }
.dash-feed__item {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto auto auto;
  align-items: center;
  gap: 12px;
  padding: 9px 0;
  border-top: 1px solid var(--line);
}
.dash-feed__item:first-child { border-top: 0; }
.dash-feed__what { color: var(--ink); text-decoration: none; font-size: 13.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-feed__what:hover { color: var(--accent); }
.dash-feed__status { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; }
.dash-feed__age { min-width: 5.5em; text-align: right; }

/* Styled tooltip, positioned by app.js; the native <title> is the fallback. */
.dash-tip {
  position: fixed;
  z-index: 50;
  pointer-events: none;
  background: var(--dash-navy);
  color: #fff;
  font-size: 12.5px;
  padding: 6px 10px;
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .2);
  white-space: nowrap;
}

@media (max-width: 1000px) {
  .dash-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .dash-row { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .dash-hero { padding: 18px 16px; border-radius: 12px; }
  .dash-hero__title { font-size: 19px; }
  .dash-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .dash-kpi__value { font-size: 22px; }
  .dash-clients { grid-template-columns: minmax(0, 1fr); }
  .dash-issue { flex-wrap: wrap; }
  /* Text takes the row beside its icons; the fix button drops underneath it. */
  .dash-issue__text { flex-basis: calc(100% - 60px); }
  .dash-issue > .btn { margin-left: 60px; }
  .dash-conn { flex-wrap: wrap; }
  .dash-conn__body { flex-basis: calc(100% - 40px); }
  .dash-conn > .btn { margin-left: 38px; }
  .dash-feed__item { grid-template-columns: 24px minmax(0, 1fr) auto; }
  .dash-feed__route, .dash-feed__age { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .dash-pulse { animation: none; }
  .dash-card, .dash-card:hover { transition: none; transform: none; }
}

/* ---------- payload and log viewers ---------- */

pre.payload, pre.logview {
  background: #12161b;
  color: #d7dde5;
  padding: 14px 16px;
  border-radius: 8px;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 620px;
}

/* ---------- board mapper ---------- */

.mapper td select { max-width: 300px; }
.mapper .arrow { color: var(--muted); text-align: center; width: 40px; }

/* ---------- login ---------- */

.login-wrap { max-width: 380px; margin: 90px auto; }
.login-wrap h1 { text-align: center; margin-bottom: 24px; }
.login-wrap .panel { padding: 24px; }
.login-wrap input { max-width: 100%; }
.login-wrap button { width: 100%; margin-top: 20px; }

/* ---------- filter bar ---------- */

.filters { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 16px; }
.filters label { margin: 0 0 5px; }
.filters select, .filters input { max-width: 200px; }

/* ---------- setup completion ---------- */

.celebrate {
  position: relative;
  overflow: hidden;          /* keeps the confetti inside the panel */
  text-align: center;
  padding: 32px 24px 28px;
  background: var(--ok-bg);
  border-color: #bfe3d0;
}
.celebrate .tick {
  width: 76px;
  height: 76px;
  margin: 0 auto 16px;
  border-radius: 50%;
  background: var(--ok);
  color: #fff;
  font-size: 40px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: celebrate-pop .55s cubic-bezier(.2, 1.5, .4, 1) both;
}
.celebrate h2 { margin: 0 0 6px; color: var(--ok); font-size: 21px; }
.celebrate p { margin: 0; }

/* Higher specificity than `.celebrate p` above, which appears later in this file
   than the .note-centred utility and therefore beat it -- taking the `auto` side
   margins with it and leaving a 640px paragraph flush left inside a panel that is
   otherwise centred. Restores the centring and the gap above it. */
.celebrate p.note-centred { margin: 14px auto 0; }

@keyframes celebrate-pop {
  from { transform: scale(.3); opacity: 0; }
  to   { transform: scale(1);  opacity: 1; }
}

/* Pieces are created by app.js; position and colour are set there. */
.confetti-piece {
  position: absolute;
  top: -16px;
  width: 8px;
  height: 14px;
  border-radius: 1px;
  pointer-events: none;
  animation: confetti-fall linear forwards;
}

@keyframes confetti-fall {
  0%   { transform: translate3d(0, -20px, 0) rotate(0deg);      opacity: 1; }
  100% { transform: translate3d(0, 340px, 0) rotate(720deg);    opacity: 0; }
}

/* Celebration is decoration. Anyone who has asked for less motion gets the
   panel and the tick without anything moving. */
@media (prefers-reduced-motion: reduce) {
  .celebrate .tick { animation: none; }
  .confetti-piece  { display: none; }
}

/* ---------- guide tabs ---------- */

.tabs {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--line);
  margin: 0 0 20px;
}
.tabs a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  text-decoration: none;
  color: var(--muted);
  font-weight: 600;
  font-size: 14px;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: 6px 6px 0 0;
  margin-bottom: -1px;
}
.tabs a:hover { color: var(--ink); }
.tabs a.on {
  background: var(--panel);
  border-color: var(--line);
  color: var(--ink);
}

/* Stroke, not fill: these are line icons and inherit the tab's colour, so the
   inactive tab's icon is muted with its label rather than staying full-strength
   and pulling the eye to the tab you are not on. */
.tabicon {
  width: 17px;
  height: 17px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .75;
}
.tabs a.on .tabicon { opacity: 1; }
/* Amber only once you are ON the teardown tab. Colouring it from the inactive
   state would read as a warning about the tab itself rather than about the mode
   you are now in. */
.tabs a.on .tabicon--teardown { stroke: var(--warn); }

/* ---------- guide client selector ---------- */

.scope-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin: 0 0 20px;
  padding: 12px 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  /* The accent edge. Carries the mode at a glance and gives the bar a left edge
     to start from, which is what it was missing -- controls floating in a wide
     empty box read as unfinished rather than as a toolbar. */
  border-left: 3px solid var(--accent);
}
.scope-bar form { display: flex; align-items: center; gap: 12px; margin: 0; }
.scope-pick { flex: 1 1 auto; min-width: 0; }

/* ---------- client avatar ---------- */

/* One class for both cases so the two are interchangeable in markup: an uploaded
   logo and an initials disc occupy the same box, and swapping one for the other
   never moves anything around it. */
.cavatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: 8px;
  overflow: hidden;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .02em;
  line-height: 1;
  color: #fff;
  /* contain, not cover: a logo is cropped by cover, and cropping somebody's
     brand mark is worse than letterboxing it. */
  object-fit: contain;
  background: #fff;
  border: 1px solid var(--line);
  user-select: none;
}
/* The initials variant paints its own background, so the neutral border that
   frames a transparent logo would only muddy it. */
.cavatar--0, .cavatar--1, .cavatar--2, .cavatar--3,
.cavatar--4, .cavatar--5, .cavatar--6, .cavatar--7 { border-color: transparent; }

/* Eight hues, deliberately no more: past this they stop being distinguishable at
   32px and start looking accidental. Each is dark enough for white text to clear
   4.5:1. */
.cavatar--0 { background: #2b6cb0; }
.cavatar--1 { background: #1f7a4d; }
.cavatar--2 { background: #9a5b00; }
.cavatar--3 { background: #a23b72; }
.cavatar--4 { background: #3f5bbf; }
.cavatar--5 { background: #0f766e; }
.cavatar--6 { background: #b4472f; }
.cavatar--7 { background: #5b4bb5; }

.cavatar--sm { width: 24px; height: 24px; border-radius: 6px; font-size: 10px; }
.cavatar--lg { width: 64px; height: 64px; border-radius: 12px; font-size: 22px; }

/* ---------- client logo field ---------- */

.logofield { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 4px; }
.logofield__controls { min-width: 0; }
.logofield__controls input[type="file"] { max-width: 100%; }
.logofield__remove { font-weight: 600; display: inline-flex; align-items: center; gap: 6px; }

.scope-text {
  display: flex;
  align-items: center;
  min-width: 0;
}
.scope-bar select {
  max-width: 320px;
  min-width: 0;
  font-weight: 600;
}

/* Pushed to the far edge: it acts on no client in particular, so grouping it
   against the picker implied it did.
   Tinted rather than solid accent -- solid would outrank the step buttons in the
   guide below, which are the actual work of the page. This only has to stop
   disappearing into the panel. */
.btn.scope-new {
  margin-left: auto;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: color-mix(in srgb, var(--accent) 9%, #fff);
  border-color: color-mix(in srgb, var(--accent) 38%, #fff);
  color: var(--accent);
}
.btn.scope-new:hover {
  background: color-mix(in srgb, var(--accent) 16%, #fff);
  border-color: var(--accent);
}
/* The plus carries the colour at full strength while the label stays readable. */
.scope-new__plus { font-size: 15px; font-weight: 700; line-height: 1; }

.scope-bar--teardown { border-left-color: var(--warn); }
/* The avatar keeps its own colour on the teardown tab. It identifies the client,
   which does not change with the mode -- the bar's edge carries the warning. */

@media (max-width: 560px) {
  /* The button drops to its own row rather than squeezing the select to nothing. */
  .scope-new { margin-left: 0; }
}

/* ---------- guide stepper ---------- */

.stepper { position: relative; margin: 0; padding: 0; list-style: none; }
/* The spine. Sits behind the numbers, inset to their centre. */
.stepper::before {
  content: "";
  position: absolute;
  left: 17px;
  top: 18px;
  bottom: 18px;
  width: 2px;
  background: var(--line);
}

.step { position: relative; padding: 0 0 26px 52px; }
.step:last-child { padding-bottom: 0; }

.step-num {
  position: absolute;
  left: 0;
  top: -2px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 14px;
  background: #fff;
  border: 2px solid var(--line);
  color: var(--muted);
  z-index: 1;
}

.step h3 { margin: 4px 0 6px; font-size: 15.5px; }
.step p { margin: 0 0 8px; }
.step .why { color: var(--muted); font-size: 13.5px; }

.step.done .step-num { background: var(--ok); border-color: var(--ok); color: #fff; }
.step.done h3 { color: var(--muted); }
.step.done h3::after {
  content: " · done";
  font-weight: 400;
  font-size: 12px;
  color: var(--ok);
  letter-spacing: .3px;
  text-transform: uppercase;
}

.step.current .step-num { background: var(--accent); border-color: var(--accent); color: #fff; }
.step.current {
  background: #f0f6fc;
  border-radius: 8px;
  margin-left: -14px;
  padding-left: 66px;
  padding-top: 10px;
  padding-bottom: 16px;
  margin-bottom: 10px;
}
.step.current .step-num { left: 14px; top: 8px; }

.step.manual .step-num { border-style: dashed; }

/* Forms are flow content and cannot sit inside <p>, so action buttons in a step
   get their own wrapper with matching spacing. */
.step-action { margin: 0 0 8px; }

.step .count {
  display: inline-block;
  background: #eef0f3;
  color: var(--muted);
  border-radius: 4px;
  padding: 1px 7px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.shortcut {
  border-left: 4px solid var(--accent);
  background: #f0f6fc;
  padding: 14px 18px;
  border-radius: 0 8px 8px 0;
  margin: 22px 0;
}
.shortcut h3 { margin: 0 0 6px; font-size: 15px; }
.shortcut p { margin: 0; font-size: 14px; }

/* RELAY lockup — dot travels the wordmark, letters light up, lands on the bar */

.relay {
  --relay-accent: #2b4eff;
  --relay-ink: #14161c;
  --relay-track: #c9ccd6;
  --relay-loop: 2.4s;

  position: relative;
  display: inline-block;
  margin: 0;
  padding: 0.08em 0.46em 0.08em 1.3em;
  font-family: system-ui, sans-serif;
  font-size: 26px;
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: 0.18em;
  color: var(--relay-ink);
  white-space: nowrap;
}

.relay__word {
  position: relative;
  z-index: 2;
}

.relay__track {
  position: absolute;
  left: 0.15em;
  top: 50%;
  width: 0.85em;
  height: 0.075em;
  margin-top: -0.04em;
  background: var(--relay-track);
}

.relay__bar {
  position: absolute;
  right: 0;
  top: 50%;
  width: 0.115em;
  height: 1.15em;
  margin-top: -0.575em;
  background: var(--relay-accent);
  animation: relay-bar var(--relay-loop) linear infinite;
}

.relay__dot {
  position: absolute;
  left: 0.15em;
  top: 50%;
  width: 0.35em;
  height: 0.35em;
  margin-top: -0.175em;
  border-radius: 50%;
  background: var(--relay-accent);
  z-index: 3;
  animation: relay-travel var(--relay-loop) cubic-bezier(0.4, 0, 0.2, 1) infinite;
}

/* one keyframe per letter, so each carries its own colour */
.relay__word span {
  animation-duration: var(--relay-loop);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.relay__word span:nth-child(1) { animation-name: relay-lit-r; animation-delay: 0.25s; }
.relay__word span:nth-child(2) { animation-name: relay-lit-e; animation-delay: 0.42s; }
.relay__word span:nth-child(3) { animation-name: relay-lit-l; animation-delay: 0.59s; }
.relay__word span:nth-child(4) { animation-name: relay-lit-a; animation-delay: 0.76s; }
.relay__word span:nth-child(5) { animation-name: relay-lit-y; animation-delay: 0.93s; }

@keyframes relay-travel {
  0%        { left: 0.15em;                opacity: 0; }
  8%        { opacity: 1; }
  66%       { left: calc(100% - 0.58em); transform: scale(1);   opacity: 1; }
  74%       { left: calc(100% - 0.58em); transform: scale(1.7); opacity: 0.4; }
  82%, 100% { left: calc(100% - 0.58em); transform: scale(1);   opacity: 0; }
}

@keyframes relay-bar {
  0%, 62% { opacity: 0.22; }
  72%     { opacity: 1; }
  100%    { opacity: 0.22; }
}

@keyframes relay-lit-r {
  0%, 100% { color: var(--relay-ink); }
  6%       { color: #4bc8fa; }
  22%      { color: var(--relay-ink); }
}
@keyframes relay-lit-e {
  0%, 100% { color: var(--relay-ink); }
  6%       { color: #4bc8fa; }
  22%      { color: var(--relay-ink); }
}
@keyframes relay-lit-l {
  0%, 100% { color: var(--relay-ink); }
  6%       { color: #4bc8fa; }
  22%      { color: var(--relay-ink); }
}
@keyframes relay-lit-a {
  0%, 100% { color: var(--relay-ink); }
  6%       { color: #4bc8fa; }
  22%      { color: var(--relay-ink); }
}
@keyframes relay-lit-y {
  0%, 100% { color: var(--relay-ink); }
  6%       { color: #4bc8fa; }
  22%      { color: var(--relay-ink); }
}

/* size variants — everything scales off font-size */
.relay--sm { font-size: 16px; letter-spacing: 0.14em; }
.relay--lg { font-size: 40px; }

@media (prefers-reduced-motion: reduce) {
  .relay__dot,
  .relay__bar,
  .relay__word span {
    animation: none;
  }
  .relay__dot {
    left: calc(100% - 0.58em);
  }
}

/* dark background variant — cyan only */
.relay--dark {
  --relay-ink: #f2f3f5;
  --relay-track: #333944;
  --relay-accent: #4bc8fa;
}

.relay--dark .relay__word span:nth-child(n) {
  animation-name: relay-lit-dark;
}

@keyframes relay-lit-dark {
  0%, 100% { color: var(--relay-ink); }
  6%       { color: var(--relay-accent); }
  22%      { color: var(--relay-ink); }
}

/* Avatar and name on one baseline in a table cell. */
.cname { display: inline-flex; align-items: center; gap: 9px; }

/* Lists set to "do not map", collapsed.
   They stay inside the form -- saving rebuilds the mapping table from what is
   posted, so a hidden row would delete its own skip. This only folds them away. */
.mapper-ignored { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 12px; }
.mapper-ignored > summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 13px;
  padding: 4px 0;
}
.mapper-ignored > summary:hover { color: var(--ink); }
.mapper-ignored table { margin-top: 8px; }

/* Import progress. The bar's width is set by app.js from data-pct: CSP
   style-src 'self' forbids a style attribute in the markup, but setting
   .style from script is allowed. Width defaults to 0, so a page with
   JavaScript off shows an empty trough rather than a full, wrong bar. */
.progress {
  height: 8px;
  background: var(--line);
  border-radius: 999px;
  overflow: hidden;
  margin: 2px 0 10px;
}
.progress__bar {
  display: block;
  width: 0;
  height: 100%;
  background: var(--accent);
  border-radius: 999px;
  transition: width .4s ease;
}
