@import url('./tokens.css');

/* Poppins -- EzyHRM's typeface, self-hosted like Gotham below rather than
   pulled from Google. The app is an internal admin tool: a webfont fetched
   from a third party at first paint is a dependency on someone else's uptime
   for the product to look like itself, and it tells that third party who is
   using it. Only the LATIN subset of each weight ships (24 KB for all three);
   the cyrillic, greek and vietnamese cuts are ~200 KB nothing here renders. */
@font-face {
  font-family: 'Poppins';
  src: url('../fonts/poppins-400.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Poppins';
  src: url('../fonts/poppins-500.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Poppins';
  src: url('../fonts/poppins-600.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}

@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothamxlight-webfont.woff2') format('woff2');
  font-weight: 200; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothamxlightitalic-webfont.woff2') format('woff2');
  font-weight: 200; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothamthin-webfont.woff2') format('woff2');
  font-weight: 300; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothamthinitalic-webfont.woff2') format('woff2');
  font-weight: 300; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothamlight-webfont.woff2') format('woff2');
  font-weight: 350; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothamlightitalic-webfont.woff2') format('woff2');
  font-weight: 350; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothambook-webfont.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothambookitalic-webfont.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothammedium_1-webfont.woff2') format('woff2');
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothambold-webfont.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothambolditalic-webfont.woff2') format('woff2');
  font-weight: 700; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'Gotham';
  src: url('../fonts/gothamblack-webfont.woff2') format('woff2');
  font-weight: 900; font-style: normal; font-display: swap;
}

body {
  margin: 0;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.55;
}

a {
  color: var(--brand-600);
}

:focus-visible {
  outline: 2px solid var(--brand-600);
  outline-offset: 2px;
}

h1:focus {
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* =========================================================================
   RMA component library
   Everything below reads tokens.css custom properties only -- no hex values
   are introduced in this file. See docs/DESIGN-TOKENS.md for the palette.
   ========================================================================= */

h1, h2, h3 {
  font-family: var(--font-sans);
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0 0 0.35em;
}

p { margin: 0 0 1em; }

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 0.9rem;
  line-height: 1;
  padding: 0.65rem 1.1rem;
  border-radius: var(--radius-field);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition: background-color 120ms ease, border-color 120ms ease, opacity 120ms ease;
}

.btn--small { padding: 0.4rem 0.75rem; font-size: 0.82rem; }

.btn--primary {
  background: var(--brand-600);
  color: var(--text-onbrand);
  box-shadow: 0 1px 2px color-mix(in srgb, var(--brand-600) 40%, transparent);
}
.btn--primary:hover {
  filter: brightness(1.08);
  box-shadow: 0 2px 8px color-mix(in srgb, var(--brand-600) 38%, transparent);
}
.btn--primary:active { filter: brightness(0.96); box-shadow: none; }

.btn--ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--text);
}
.btn--ghost:hover { background: var(--surface-sunk); }

.btn--danger {
  background: transparent;
  border-color: var(--error);
  color: var(--error);
}
.btn--danger:hover { background: color-mix(in srgb, var(--error) 12%, transparent); }

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}
.btn:disabled:hover,
.btn[aria-disabled="true"]:hover { filter: none; background: transparent; }

.btn--primary:disabled { background: var(--brand-600); }

/* -- Theme toggle --------------------------------------------------------
   Cycles system -> light -> dark -> system (wwwroot/js/theme.js). Only one
   icon is shown at a time, chosen by the data-theme-current attribute the
   script keeps on the button. Reduced-motion is already handled by the
   global @media rule above (it zeroes every transition-duration on the
   page, this one included), and this is a plain <button> so it's
   keyboard-operable (Tab to focus, Enter/Space to activate) with no extra
   markup needed. ------------------------------------------------------- */

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  flex: none;
  padding: 0;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  transition: background-color 120ms ease, color 120ms ease;
}
.theme-toggle:hover { background: var(--surface-sunk); color: var(--text); }

.theme-toggle__icon { display: none; }

/* Before theme.js runs, the button has no data-theme-current yet -- default
   to showing the "system" glyph so there's never a blank button. */
.theme-toggle:not([data-theme-current]) .theme-toggle__icon--system,
.theme-toggle[data-theme-current="system"] .theme-toggle__icon--system,
.theme-toggle[data-theme-current="light"] .theme-toggle__icon--light,
.theme-toggle[data-theme-current="dark"] .theme-toggle__icon--dark {
  display: inline;
}

/* -- Form fields ------------------------------------------------------------ */

.field {
  display: block;
  margin-bottom: 1.1rem;
}

.field__label {
  display: block;
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text-muted);
  margin-bottom: 0.4rem;
}

.field__hint {
  display: block;
  font-size: 0.78rem;
  color: var(--text-muted);
  margin-top: 0.35rem;
}

/* EzyHRM's fields are noticeably rounder than a default control (13px on a
   ~40px box) and that roundness is most of what makes its forms recognisable.
   --radius-field carries it so a control can be round without every card and
   button inheriting the same curve. */
.input,
.select {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--font-sans);
  font-size: 0.95rem;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-field);
  padding: 0.65rem 0.85rem;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}

.input:hover:not(:disabled),
.select:hover:not(:disabled) { border-color: var(--brand-400); }

/* The outline STAYS. EzyHRM signals focus with a coloured border and a soft
   glow, which is pretty but is a border change -- Windows High Contrast strips
   backgrounds and shadows, so a focus state made only of those disappears for
   the people who most need it. The outline is the accessible signal; the glow
   is the EzyHRM look layered on top of it, not instead of it. */
.input:focus-visible,
.select:focus-visible {
  outline: 2px solid var(--brand-600);
  outline-offset: 1px;
  border-color: var(--brand-600);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand-300) 22%, transparent);
}

.input.invalid { border-color: var(--error); }

.validation-message,
.field-validation-error {
  display: block;
  color: var(--error);
  font-size: 0.8rem;
  margin-top: 0.35rem;
}

/* -- Dashboard stats --------------------------------------------------------- */

/* auto-fit rather than a fixed column count: the row goes 4 -> 2 -> 1 on its
   own as the viewport narrows, with no breakpoint to keep in step with the
   375px sweep. */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1rem;
}

.stat {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 1.6);
  padding: 1.1rem 1.25rem;
  box-shadow: var(--shadow-1);
}

.stat__value {
  font-size: 1.9rem;
  font-weight: 600;
  line-height: 1.1;
  color: var(--text);
  /* Figures line up between tiles, and a count that ticks upward while a
     campaign sends does not jiggle its own label. */
  font-variant-numeric: tabular-nums;
}

.stat__label {
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* -- Cards ------------------------------------------------------------------ */

.card {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 1.6);
  box-shadow: var(--shadow-1);
  padding: 1.75rem;
}

/* -- Create-lead disclosure --------------------------------------------------- */

.create-lead {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 1.5);
  box-shadow: var(--shadow-1);
  padding: 0.9rem 1.75rem;
  margin-bottom: 1.5rem;
}

.create-lead > summary {
  cursor: pointer;
  font-weight: 500;
  color: var(--text);
  padding: 0.4rem 0;
  list-style: none;
}

.create-lead > summary::-webkit-details-marker { display: none; }

.create-lead > summary::before {
  content: '▸';
  display: inline-block;
  margin-right: 0.5rem;
  color: var(--text-muted);
  transition: transform 0.15s ease;
}

.create-lead[open] > summary::before { transform: rotate(90deg); }

.create-lead[open] > summary {
  margin-bottom: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border);
}

.create-lead form { padding-top: 0.25rem; }

/* -- Banners / alerts -------------------------------------------------------- */

.banner {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
  font-size: 0.88rem;
  margin-bottom: 1.1rem;
}

.banner--error {
  background: color-mix(in srgb, var(--error) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--error) 35%, var(--surface));
  color: var(--error);
}

.banner--info {
  background: color-mix(in srgb, var(--info) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--info) 35%, var(--surface));
  color: var(--text);
}

.banner--success {
  background: color-mix(in srgb, var(--ok) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--ok) 35%, var(--surface));
  color: var(--text);
}

.banner--warn {
  background: color-mix(in srgb, var(--warn) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--surface));
  color: var(--text);
}

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

.chip {
  display: inline-flex;
  align-items: center;
  font-size: 0.76rem;
  font-weight: 500;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  background: var(--brand-100);
  color: var(--brand-900);
  white-space: nowrap;
}

/* `.badge` was used in the markup but never defined, so an unsubscribed
   marker rendered as bare text butted straight against the address it
   qualifies -- "howard.huang@rmagroup.com.sgunsubscribed", which reads as a
   malformed address rather than as a status. The margin is the actual fix;
   the rest makes it look like the status it is, borrowing `.chip--warn`'s
   palette because that is what this badge means. */
.badge {
  display: inline-flex;
  align-items: center;
  margin-left: 0.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--warn) 16%, var(--surface));
  color: var(--warn);
  white-space: nowrap;
  vertical-align: middle;
}

.chip--muted {
  background: var(--surface-sunk);
  color: var(--text-muted);
  border: 1px solid var(--border);
}

.chip--warn {
  background: color-mix(in srgb, var(--warn) 16%, var(--surface));
  color: var(--warn);
}

.chip--removable {
  gap: 0.35rem;
  padding-right: 0.35rem;
}

.chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.05rem;
  height: 1.05rem;
  line-height: 1;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--brand-900);
  cursor: pointer;
  font-size: 0.9rem;
}

.chip__remove:hover { background: color-mix(in srgb, var(--brand-900) 15%, transparent); }

.chip__remove:disabled {
  cursor: not-allowed;
  opacity: 0.4;
}
.chip__remove:disabled:hover { background: transparent; }

.chip-row { display: flex; flex-wrap: wrap; gap: 0.35rem; }

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

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.data-table th {
  text-align: left;
  font-size: 0.76rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  font-weight: 500;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--border);
}

.data-table td {
  padding: 0.75rem;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}

.data-table tr:last-child td { border-bottom: none; }

.data-table .row-actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
}

/* Counts. Right-aligned and tabular so a column of numbers lines up on its
   digits -- a recipient count read against the one above it is the whole
   reason /reports puts them in a table. */
.data-table th.num,
.data-table td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* A paragraph that qualifies the table under it -- used by /reports to say
   that the archived runs never passed this system's controls. Muted, but not
   small: it changes how the numbers beneath it should be read. */
.section-note {
  color: var(--text-muted);
  margin: 0 0 1rem;
  max-width: 70ch;
}

/* `position: relative` is not decoration -- it makes this element the
   containing block for absolutely positioned descendants, so they are clipped
   by the scroll box like everything else.
   Without it, the `.visually-hidden` span inside a wide table's <th> resolved
   its position against an ancestor OUTSIDE this wrapper, escaped the clip, and
   landed 86px past a 375px viewport -- giving the whole document a horizontal
   scrollbar while every element in the width chain measured exactly 375.
   Measured, not guessed: the span's right edge was 461px and
   documentElement.scrollWidth was 461px. The table itself was contained
   correctly the entire time and is not the culprit, however much it looks
   like one at 550px wide. */
.table-wrap { overflow-x: auto; position: relative; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.permission-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 0.5rem 1rem;
  margin: 0.5rem 0 0.25rem;
}

.permission-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  color: var(--text);
}

.permission-check input[type="checkbox"] {
  width: 1rem;
  height: 1rem;
  accent-color: var(--brand-600);
}

/* -- Page scaffolding for admin screens --------------------------------------- */

.page-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}

.page-head p { color: var(--text-muted); margin: 0.2rem 0 0; }

.section-title {
  font-size: 1rem;
  font-weight: 700;
  margin: 2rem 0 0.75rem;
}

/* -- Stat grid ------------------------------------------------------------------
   Spec §9's live delivery panel: six counts read at a glance, side by side.
   auto-fit + minmax means the row reflows to two or three columns on a phone
   instead of collapsing into one tall unreadable stack -- the failure mode a
   .banner (display:flex, no wrap) already produced twice on this project. Each
   tile is its own flex COLUMN so the label sits above its number; the grid,
   not the tile, is what runs across. --- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.75rem;
}

.stat-grid > div {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-sunk);
}

.stat__label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.stat__value {
  font-size: 1.35rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The resolution controls in a NeedsReview row: a note field and two verbs.
   Wrapped so the two buttons stay beside the evidence they belong to rather
   than wrapping under a neighbouring row's, and so a narrow viewport stacks
   them instead of forcing the whole table sideways. */

.resolve-cell {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.resolve-cell .input { min-width: 12rem; flex: 1 1 12rem; }

/* -- Toolbars / search --------------------------------------------------------- */

.toolbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}

.toolbar .field {
  margin-bottom: 0;
  flex: 1 1 18rem;
  max-width: 22rem;
}

/* -- Pagination ----------------------------------------------------------------- */

.pagination {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}

/* -- Empty states ---------------------------------------------------------------
   An empty table is a prompt, not a dead end -- it names the next action. */

.empty-state {
  text-align: center;
  padding: 3rem 1.5rem;
  color: var(--text-muted);
  border: 1px dashed var(--border);
  border-radius: calc(var(--radius) * 1.5);
}

.empty-state h3 {
  color: var(--text);
  margin-bottom: 0.4rem;
}

.empty-state .btn { margin-top: 1rem; }

/* -- Import manifest --------------------------------------------------------------
   The rejection list reads like an audit ledger, not a wall of red text: a
   monospace line-number tag anchors each entry so "line 7" is scannable at a
   glance, a left rule in --error carries the eye down the column, and the
   whole thing scrolls independently of the page so a 500-row rejection list
   never pushes the confirm step off screen. This is the one screen in the
   app where showing the reader everything, unsummarised, is the point. --- */

.manifest {
  max-height: 22rem;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-sunk);
}

.manifest__row {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.6rem 0.85rem;
  border-left: 3px solid var(--error);
  border-bottom: 1px solid var(--border);
}

.manifest__row:last-child { border-bottom: none; }

.manifest__line {
  flex: none;
  font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--error);
  background: color-mix(in srgb, var(--error) 12%, transparent);
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  white-space: nowrap;
}

.manifest__reason {
  font-size: 0.85rem;
  color: var(--text);
}

/* -- Import summary / confirm bar ------------------------------------------------ */

.import-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
  margin-top: 1.25rem;
}

.import-summary__counts {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.import-summary__count {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.import-summary__count strong { font-size: 1.3rem; line-height: 1; }
.import-summary__count span { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted); }

.import-summary__actions { display: flex; gap: 0.6rem; }

.csv-input-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1.25rem;
}

@media (max-width: 760px) {
  .csv-input-grid { grid-template-columns: 1fr; }
}

.csv-input-grid textarea.input {
  font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
  font-size: 0.82rem;
  min-height: 10rem;
  resize: vertical;
}

.file-drop {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 1.25rem;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  justify-content: center;
}

/* -- Approval artefact ------------------------------------------------------------
   The frozen subject and body a checker is being asked to approve, shown on the
   approvals queue card. Rendered as TEXT, never as markup: these strings are
   author-written HTML and the queue's job is to show what they say, not to run
   them.

   Two layout rules do the real work. `white-space: pre-wrap` keeps the author's
   own line breaks (a body is written in lines and reads wrongly reflowed into one
   paragraph) while still wrapping long lines, and `overflow-wrap: anywhere` means
   a 4,000-character URL with no spaces in it breaks rather than widening the card
   and taking the whole page's horizontal scrollbar with it. The height cap keeps a
   long body from burying the Approve button and the next campaign in the queue
   below it; the block scrolls itself, exactly as .manifest does. --- */

.approval-artefact {
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand-600);
  border-radius: var(--radius);
  background: var(--surface-sunk);
  padding: 0.85rem 1rem;
  margin: 0.75rem 0 1rem;
  max-width: 100%;
  overflow: hidden;   /* nothing inside may widen the card */
}

.approval-artefact__label {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  margin: 0 0 0.35rem;
}

.approval-artefact__subject {
  margin: 0 0 0.6rem;
  overflow-wrap: anywhere;
}

/* Preview first, source on demand. The checkbox is the toggle so the page
   needs no script -- this queue is the one screen where an approver must be
   able to trust what they are looking at. */
.approval-view { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

.approval-view__toggle {
  display: inline-block;
  margin: 0 0 0.6rem 0;
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--brand-600);
  cursor: pointer;
  text-decoration: underline;
}
.approval-view:focus-visible + .approval-view__toggle {
  outline: 2px solid var(--brand-600);
  outline-offset: 2px;
}

.approval-artefact__frame {
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
  background: #ffffff;   /* the mail's own ground, not the app's -- this is a preview of an email */
}
.approval-artefact__frame iframe {
  display: block;
  width: 100%;
  height: 420px;
  border: 0;
}

/* Source is hidden until asked for, and then the frame steps aside so the two
   are never argued over side by side. */
.approval-artefact__body { display: none; }
.approval-view:checked ~ .approval-artefact__frame { display: none; }
.approval-view:checked ~ .approval-artefact__body { display: block; }

.approval-artefact__body {
  margin: 0;
  max-height: 18rem;
  overflow: auto;
  font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* -- Access denied ------------------------------------------------------------- */

.denied {
  max-width: 32rem;
  margin: 4rem auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  align-items: center;
}
.denied p { color: var(--text-muted); }

/* -- Auth screens (login / forced rotation) ------------------------------------
   A fixed dark-green "ledger" panel plus a light form panel. --brand-900 and
   --brand-100 are not redefined for dark mode (see tokens.css), so this pairing
   holds its own contrast in all three viewer states without a special dark
   branch -- it is a deliberate constant, not a light-mode default that forgot
   about dark mode. ------------------------------------------------------------ */

.auth-shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(320px, 480px);
}

.auth-brand {
  background: var(--brand-900);
  color: var(--brand-100);
  padding: clamp(2rem, 5vw, 4rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1.25rem;
}

.auth-brand__mark {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.auth-brand__logo {
  /* Fixed box so the panel does not reflow between the SVG arriving and the
     text, which on a slow connection is the first thing a user sees. */
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  display: block;
}

.auth-brand__wordmark {
  font-weight: 900;
  font-size: 1.1rem;
  letter-spacing: 0.06em;
  color: var(--brand-300);
}

.auth-brand__eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--brand-400);
  margin: 0;
}

.auth-brand__title {
  font-size: clamp(1.6rem, 3vw, 2.3rem);
  font-weight: 700;
  color: var(--brand-100);
  max-width: 22ch;
  margin: 0;
}

.auth-brand__lede {
  color: color-mix(in srgb, var(--brand-100) 78%, transparent);
  max-width: 40ch;
  font-size: 0.95rem;
  margin: 0;
}

.auth-ledger {
  margin: 0.75rem 0 0;
  max-height: 9.5rem;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, black 70%, transparent 100%);
  mask-image: linear-gradient(to bottom, black 70%, transparent 100%);
}

.auth-ledger__track {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.auth-ledger__row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-family: ui-monospace, 'SF Mono', 'Cascadia Code', Consolas, monospace;
  font-size: 0.8rem;
  color: color-mix(in srgb, var(--brand-100) 85%, transparent);
}

.auth-ledger__dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--brand-300);
  flex: none;
}

.auth-ledger__flag {
  font-family: var(--font-sans);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 22%, var(--brand-900));
  padding: 0.05rem 0.4rem;
  border-radius: 999px;
}

@media (prefers-reduced-motion: no-preference) {
  .auth-ledger__track {
    animation: auth-ledger-scroll 26s linear infinite;
  }
  .auth-ledger:hover .auth-ledger__track,
  .auth-ledger:focus-within .auth-ledger__track {
    animation-play-state: paused;
  }
}

@keyframes auth-ledger-scroll {
  from { transform: translateY(0); }
  to   { transform: translateY(-50%); }
}

.auth-panel {
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}

.auth-card {
  width: 100%;
  max-width: 22rem;
}

.auth-card h2 {
  font-size: 1.4rem;
  margin-bottom: 0.3rem;
}

.auth-card__sub {
  color: var(--text-muted);
  font-size: 0.9rem;
  margin-bottom: 1.5rem;
}

.auth-card .btn { width: 100%; margin-top: 0.25rem; }

.auth-explain {
  background: var(--surface-sunk);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  font-size: 0.85rem;
  color: var(--text-muted);
  margin-bottom: 1.5rem;
}

@media (max-width: 860px) {
  .auth-shell { grid-template-columns: 1fr; }
  .auth-brand { padding: 2rem 1.5rem; min-height: 0; }
  .auth-ledger { display: none; }
  .auth-panel { padding: 2rem 1.5rem 3rem; }
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--ok);
}

.invalid {
    outline: 1px solid var(--error);
}

.validation-message {
    color: var(--error);
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, var(--error);
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: var(--border);
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ---- The composer (plan "composer", Task 6) --------------------------------
   Quill ships its own visual theme (vendor/quill/quill.snow.css); everything
   here is layout this application owns.

   The height matters. Quill's own `.ql-container { height: 100% }` resolves to
   `auto` inside a parent with no height of its own, so an empty editor
   collapses to a two-line box that does not look like somewhere to write a
   campaign. The min-height matches the `rows="16"` of the source textarea it
   replaces, so switching format does not make the page jump. */
.composer__editor .ql-container {
    min-height: 320px;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    background: var(--surface, #fff);
}

.composer__editor .ql-toolbar {
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
}

/* The Convert diff: two texts, side by side wide and stacked narrow, each
   scrolling on its own. `pre` without a scroll container is how a long HTML
   body pushes a page into horizontal overflow (A-549 exists for that). */
.convert-diff {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    margin: 0.5rem 0;
}

@media (min-width: 900px) {
    .convert-diff {
        grid-template-columns: 1fr 1fr;
    }
}

    .convert-diff pre {
        overflow: auto;
        max-height: 18rem;
        padding: 0.5rem;
        border: 1px solid var(--border);
        border-radius: 4px;
        white-space: pre-wrap;
        word-break: break-word;
        font-size: 0.8rem;
    }

    .convert-diff h3 {
        font-size: 0.9rem;
        margin: 0 0 0.25rem;
    }

/* ── The lead-list ribbon (/leads) ────────────────────────────────────────
   A horizontal tab strip: "All" plus one tab per lead list.

   🔑 overflow-x:auto is the load-bearing line, and it is the same decision
   .table-wrap makes a few hundred lines up. A strip whose width grows with
   the number of lists MUST scroll inside its own box; without that, twelve
   lists widen the document and every page on the site gains a horizontal
   scrollbar at 375px. Leads/ListDetail.razor shipped exactly that defect --
   122px of overflow on every single run -- and no test caught it until one
   measured the document rather than the element. A-770 is that guard here.

   min-width:0 on the flex child is what actually lets the scroll happen:
   without it a flex item refuses to shrink below its content and the
   overflow escapes to the page instead of staying in the strip. */
.ribbon {
  display: flex;
  gap: 0.25rem;
  overflow-x: auto;
  min-width: 0;
  padding-bottom: 0.25rem;
  margin-bottom: 0.75rem;
  border-bottom: 1px solid var(--border);
  /* Momentum scrolling on touch, and no scroll-chaining to the page. */
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

.ribbon__tab {
  flex: 0 0 auto;              /* never squash a tab to fit -- scroll instead */
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.45rem 0.8rem;
  border-radius: var(--radius-field, 8px) var(--radius-field, 8px) 0 0;
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.9rem;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
}

.ribbon__tab:hover { background: var(--surface-sunk); color: var(--text); }

/* The current tab is marked by colour AND a 2px underline, never by colour
   alone -- aria-current carries it for assistive tech, and the underline
   carries it for anyone who cannot separate these two blues. */
.ribbon__tab--current {
  color: var(--brand-700);
  font-weight: 600;
  border-bottom-color: var(--brand-600);
}

.ribbon__count { color: var(--text-muted); font-variant-numeric: tabular-nums; }
.ribbon__tab--current .ribbon__count { color: inherit; }
